duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

imgui_widgets.cpp (502956B)


      1 // dear imgui, v1.91.0
      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: Typing-Select support
     22 // [SECTION] Widgets: Box-Select support
     23 // [SECTION] Widgets: Multi-Select support
     24 // [SECTION] Widgets: Multi-Select helpers
     25 // [SECTION] Widgets: ListBox
     26 // [SECTION] Widgets: PlotLines, PlotHistogram
     27 // [SECTION] Widgets: Value helpers
     28 // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
     29 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
     30 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
     31 // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
     32 
     33 */
     34 
     35 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
     36 #define _CRT_SECURE_NO_WARNINGS
     37 #endif
     38 
     39 #ifndef IMGUI_DEFINE_MATH_OPERATORS
     40 #define IMGUI_DEFINE_MATH_OPERATORS
     41 #endif
     42 
     43 #include "imgui.h"
     44 #ifndef IMGUI_DISABLE
     45 #include "imgui_internal.h"
     46 
     47 // System includes
     48 #include <stdint.h>     // intptr_t
     49 
     50 //-------------------------------------------------------------------------
     51 // Warnings
     52 //-------------------------------------------------------------------------
     53 
     54 // Visual Studio warnings
     55 #ifdef _MSC_VER
     56 #pragma warning (disable: 4127)     // condition expression is constant
     57 #pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
     58 #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
     59 #pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types
     60 #endif
     61 #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).
     62 #pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
     63 #endif
     64 
     65 // Clang/GCC warnings with -Weverything
     66 #if defined(__clang__)
     67 #if __has_warning("-Wunknown-warning-option")
     68 #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!
     69 #endif
     70 #pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
     71 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
     72 #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.
     73 #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.
     74 #pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
     75 #pragma clang diagnostic ignored "-Wunused-macros"                  // warning: macro is not used                                // we define snprintf/vsnprintf on Windows so they are available, but not always used.
     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 #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
     82 #elif defined(__GNUC__)
     83 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
     84 #pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
     85 #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
     86 #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"  // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
     87 #endif
     88 
     89 //-------------------------------------------------------------------------
     90 // Data
     91 //-------------------------------------------------------------------------
     92 
     93 // Widgets
     94 static const float          DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;    // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
     95 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.
     96 
     97 // Those MIN/MAX values are not define because we need to point to them
     98 static const signed char    IM_S8_MIN  = -128;
     99 static const signed char    IM_S8_MAX  = 127;
    100 static const unsigned char  IM_U8_MIN  = 0;
    101 static const unsigned char  IM_U8_MAX  = 0xFF;
    102 static const signed short   IM_S16_MIN = -32768;
    103 static const signed short   IM_S16_MAX = 32767;
    104 static const unsigned short IM_U16_MIN = 0;
    105 static const unsigned short IM_U16_MAX = 0xFFFF;
    106 static const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
    107 static const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
    108 static const ImU32          IM_U32_MIN = 0;
    109 static const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
    110 #ifdef LLONG_MIN
    111 static const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
    112 static const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
    113 #else
    114 static const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;
    115 static const ImS64          IM_S64_MAX = 9223372036854775807LL;
    116 #endif
    117 static const ImU64          IM_U64_MIN = 0;
    118 #ifdef ULLONG_MAX
    119 static const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
    120 #else
    121 static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
    122 #endif
    123 
    124 //-------------------------------------------------------------------------
    125 // [SECTION] Forward Declarations
    126 //-------------------------------------------------------------------------
    127 
    128 // For InputTextEx()
    129 static bool     InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
    130 static int      InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
    131 static ImVec2   InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
    132 
    133 //-------------------------------------------------------------------------
    134 // [SECTION] Widgets: Text, etc.
    135 //-------------------------------------------------------------------------
    136 // - TextEx() [Internal]
    137 // - TextUnformatted()
    138 // - Text()
    139 // - TextV()
    140 // - TextColored()
    141 // - TextColoredV()
    142 // - TextDisabled()
    143 // - TextDisabledV()
    144 // - TextWrapped()
    145 // - TextWrappedV()
    146 // - LabelText()
    147 // - LabelTextV()
    148 // - BulletText()
    149 // - BulletTextV()
    150 //-------------------------------------------------------------------------
    151 
    152 void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
    153 {
    154     ImGuiWindow* window = GetCurrentWindow();
    155     if (window->SkipItems)
    156         return;
    157     ImGuiContext& g = *GImGui;
    158 
    159     // Accept null ranges
    160     if (text == text_end)
    161         text = text_end = "";
    162 
    163     // Calculate length
    164     const char* text_begin = text;
    165     if (text_end == NULL)
    166         text_end = text + strlen(text); // FIXME-OPT
    167 
    168     const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
    169     const float wrap_pos_x = window->DC.TextWrapPos;
    170     const bool wrap_enabled = (wrap_pos_x >= 0.0f);
    171     if (text_end - text <= 2000 || wrap_enabled)
    172     {
    173         // Common case
    174         const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
    175         const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
    176 
    177         ImRect bb(text_pos, text_pos + text_size);
    178         ItemSize(text_size, 0.0f);
    179         if (!ItemAdd(bb, 0))
    180             return;
    181 
    182         // Render (we don't hide text after ## in this end-user function)
    183         RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
    184     }
    185     else
    186     {
    187         // Long text!
    188         // Perform manual coarse clipping to optimize for long multi-line text
    189         // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
    190         // - 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.
    191         // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
    192         const char* line = text;
    193         const float line_height = GetTextLineHeight();
    194         ImVec2 text_size(0, 0);
    195 
    196         // Lines to skip (can't skip when logging text)
    197         ImVec2 pos = text_pos;
    198         if (!g.LogEnabled)
    199         {
    200             int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
    201             if (lines_skippable > 0)
    202             {
    203                 int lines_skipped = 0;
    204                 while (line < text_end && lines_skipped < lines_skippable)
    205                 {
    206                     const char* line_end = (const char*)memchr(line, '\n', text_end - line);
    207                     if (!line_end)
    208                         line_end = text_end;
    209                     if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
    210                         text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
    211                     line = line_end + 1;
    212                     lines_skipped++;
    213                 }
    214                 pos.y += lines_skipped * line_height;
    215             }
    216         }
    217 
    218         // Lines to render
    219         if (line < text_end)
    220         {
    221             ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
    222             while (line < text_end)
    223             {
    224                 if (IsClippedEx(line_rect, 0))
    225                     break;
    226 
    227                 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
    228                 if (!line_end)
    229                     line_end = text_end;
    230                 text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
    231                 RenderText(pos, line, line_end, false);
    232                 line = line_end + 1;
    233                 line_rect.Min.y += line_height;
    234                 line_rect.Max.y += line_height;
    235                 pos.y += line_height;
    236             }
    237 
    238             // Count remaining lines
    239             int lines_skipped = 0;
    240             while (line < text_end)
    241             {
    242                 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
    243                 if (!line_end)
    244                     line_end = text_end;
    245                 if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
    246                     text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
    247                 line = line_end + 1;
    248                 lines_skipped++;
    249             }
    250             pos.y += lines_skipped * line_height;
    251         }
    252         text_size.y = (pos - text_pos).y;
    253 
    254         ImRect bb(text_pos, text_pos + text_size);
    255         ItemSize(text_size, 0.0f);
    256         ItemAdd(bb, 0);
    257     }
    258 }
    259 
    260 void ImGui::TextUnformatted(const char* text, const char* text_end)
    261 {
    262     TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
    263 }
    264 
    265 void ImGui::Text(const char* fmt, ...)
    266 {
    267     va_list args;
    268     va_start(args, fmt);
    269     TextV(fmt, args);
    270     va_end(args);
    271 }
    272 
    273 void ImGui::TextV(const char* fmt, va_list args)
    274 {
    275     ImGuiWindow* window = GetCurrentWindow();
    276     if (window->SkipItems)
    277         return;
    278 
    279     const char* text, *text_end;
    280     ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
    281     TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
    282 }
    283 
    284 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
    285 {
    286     va_list args;
    287     va_start(args, fmt);
    288     TextColoredV(col, fmt, args);
    289     va_end(args);
    290 }
    291 
    292 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
    293 {
    294     PushStyleColor(ImGuiCol_Text, col);
    295     TextV(fmt, args);
    296     PopStyleColor();
    297 }
    298 
    299 void ImGui::TextDisabled(const char* fmt, ...)
    300 {
    301     va_list args;
    302     va_start(args, fmt);
    303     TextDisabledV(fmt, args);
    304     va_end(args);
    305 }
    306 
    307 void ImGui::TextDisabledV(const char* fmt, va_list args)
    308 {
    309     ImGuiContext& g = *GImGui;
    310     PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
    311     TextV(fmt, args);
    312     PopStyleColor();
    313 }
    314 
    315 void ImGui::TextWrapped(const char* fmt, ...)
    316 {
    317     va_list args;
    318     va_start(args, fmt);
    319     TextWrappedV(fmt, args);
    320     va_end(args);
    321 }
    322 
    323 void ImGui::TextWrappedV(const char* fmt, va_list args)
    324 {
    325     ImGuiContext& g = *GImGui;
    326     const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
    327     if (need_backup)
    328         PushTextWrapPos(0.0f);
    329     TextV(fmt, args);
    330     if (need_backup)
    331         PopTextWrapPos();
    332 }
    333 
    334 void ImGui::LabelText(const char* label, const char* fmt, ...)
    335 {
    336     va_list args;
    337     va_start(args, fmt);
    338     LabelTextV(label, fmt, args);
    339     va_end(args);
    340 }
    341 
    342 // Add a label+text combo aligned to other label+value widgets
    343 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
    344 {
    345     ImGuiWindow* window = GetCurrentWindow();
    346     if (window->SkipItems)
    347         return;
    348 
    349     ImGuiContext& g = *GImGui;
    350     const ImGuiStyle& style = g.Style;
    351     const float w = CalcItemWidth();
    352 
    353     const char* value_text_begin, *value_text_end;
    354     ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
    355     const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
    356     const ImVec2 label_size = CalcTextSize(label, NULL, true);
    357 
    358     const ImVec2 pos = window->DC.CursorPos;
    359     const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
    360     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));
    361     ItemSize(total_bb, style.FramePadding.y);
    362     if (!ItemAdd(total_bb, 0))
    363         return;
    364 
    365     // Render
    366     RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
    367     if (label_size.x > 0.0f)
    368         RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
    369 }
    370 
    371 void ImGui::BulletText(const char* fmt, ...)
    372 {
    373     va_list args;
    374     va_start(args, fmt);
    375     BulletTextV(fmt, args);
    376     va_end(args);
    377 }
    378 
    379 // Text with a little bullet aligned to the typical tree node.
    380 void ImGui::BulletTextV(const char* fmt, va_list args)
    381 {
    382     ImGuiWindow* window = GetCurrentWindow();
    383     if (window->SkipItems)
    384         return;
    385 
    386     ImGuiContext& g = *GImGui;
    387     const ImGuiStyle& style = g.Style;
    388 
    389     const char* text_begin, *text_end;
    390     ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
    391     const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
    392     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
    393     ImVec2 pos = window->DC.CursorPos;
    394     pos.y += window->DC.CurrLineTextBaseOffset;
    395     ItemSize(total_size, 0.0f);
    396     const ImRect bb(pos, pos + total_size);
    397     if (!ItemAdd(bb, 0))
    398         return;
    399 
    400     // Render
    401     ImU32 text_col = GetColorU32(ImGuiCol_Text);
    402     RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
    403     RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
    404 }
    405 
    406 //-------------------------------------------------------------------------
    407 // [SECTION] Widgets: Main
    408 //-------------------------------------------------------------------------
    409 // - ButtonBehavior() [Internal]
    410 // - Button()
    411 // - SmallButton()
    412 // - InvisibleButton()
    413 // - ArrowButton()
    414 // - CloseButton() [Internal]
    415 // - CollapseButton() [Internal]
    416 // - GetWindowScrollbarID() [Internal]
    417 // - GetWindowScrollbarRect() [Internal]
    418 // - Scrollbar() [Internal]
    419 // - ScrollbarEx() [Internal]
    420 // - Image()
    421 // - ImageButton()
    422 // - Checkbox()
    423 // - CheckboxFlagsT() [Internal]
    424 // - CheckboxFlags()
    425 // - RadioButton()
    426 // - ProgressBar()
    427 // - Bullet()
    428 // - Hyperlink()
    429 //-------------------------------------------------------------------------
    430 
    431 // The ButtonBehavior() function is key to many interactions and used by many/most widgets.
    432 // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
    433 // this code is a little complex.
    434 // By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
    435 // See the series of events below and the corresponding state reported by dear imgui:
    436 //------------------------------------------------------------------------------------------------------------------------------------------------
    437 // with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    438 //   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -
    439 //   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -
    440 //   Frame N+2 (mouse button is down)       -             true             true            true               -                    true
    441 //   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
    442 //   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -
    443 //   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -
    444 //   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -
    445 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    446 //   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -
    447 //------------------------------------------------------------------------------------------------------------------------------------------------
    448 // with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    449 //   Frame N+2 (mouse button is down)       true          true             true            true               -                    true
    450 //   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
    451 //   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
    452 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    453 //------------------------------------------------------------------------------------------------------------------------------------------------
    454 // with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    455 //   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true
    456 //   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -
    457 //   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -
    458 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    459 //------------------------------------------------------------------------------------------------------------------------------------------------
    460 // with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
    461 //   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true
    462 //   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -
    463 //   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -
    464 //   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -
    465 //   Frame N+4 (mouse button is down)       true          true             true            true               -                    true
    466 //   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -
    467 //   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
    468 //   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
    469 //------------------------------------------------------------------------------------------------------------------------------------------------
    470 // Note that some combinations are supported,
    471 // - PressedOnDragDropHold can generally be associated with any flag.
    472 // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
    473 //------------------------------------------------------------------------------------------------------------------------------------------------
    474 // The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
    475 //                                         Repeat+                  Repeat+           Repeat+             Repeat+
    476 //                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick
    477 //-------------------------------------------------------------------------------------------------------------------------------------------------
    478 //   Frame N+0 (mouse button is down)       -                        true              -                   true
    479 //   ...                                    -                        -                 -                   -
    480 //   Frame N + RepeatDelay                  true                     true              -                   true
    481 //   ...                                    -                        -                 -                   -
    482 //   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true
    483 //-------------------------------------------------------------------------------------------------------------------------------------------------
    484 
    485 // FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc.
    486 // And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
    487 // For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
    488 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
    489 {
    490     ImGuiContext& g = *GImGui;
    491     ImGuiWindow* window = GetCurrentWindow();
    492 
    493     // Default only reacts to left mouse button
    494     if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
    495         flags |= ImGuiButtonFlags_MouseButtonLeft;
    496 
    497     // Default behavior requires click + release inside bounding box
    498     if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
    499         flags |= ImGuiButtonFlags_PressedOnDefault_;
    500 
    501     // Default behavior inherited from item flags
    502     // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
    503     ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags);
    504     if (flags & ImGuiButtonFlags_AllowOverlap)
    505         item_flags |= ImGuiItemFlags_AllowOverlap;
    506     if (flags & ImGuiButtonFlags_Repeat)
    507         item_flags |= ImGuiItemFlags_ButtonRepeat;
    508 
    509     ImGuiWindow* backup_hovered_window = g.HoveredWindow;
    510     const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window;
    511     if (flatten_hovered_children)
    512         g.HoveredWindow = window;
    513 
    514 #ifdef IMGUI_ENABLE_TEST_ENGINE
    515     // Alternate registration spot, for when caller didn't use ItemAdd()
    516     if (g.LastItemData.ID != id)
    517         IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
    518 #endif
    519 
    520     bool pressed = false;
    521     bool hovered = ItemHoverable(bb, id, item_flags);
    522 
    523     // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
    524     if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
    525         if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
    526         {
    527             hovered = true;
    528             SetHoveredID(id);
    529             if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
    530             {
    531                 pressed = true;
    532                 g.DragDropHoldJustPressedId = id;
    533                 FocusWindow(window);
    534             }
    535         }
    536 
    537     if (flatten_hovered_children)
    538         g.HoveredWindow = backup_hovered_window;
    539 
    540     // Mouse handling
    541     const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
    542     if (hovered)
    543     {
    544         IM_ASSERT(id != 0); // Lazily check inside rare path.
    545 
    546         // Poll mouse buttons
    547         // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
    548         // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.
    549         int mouse_button_clicked = -1;
    550         int mouse_button_released = -1;
    551         for (int button = 0; button < 3; button++)
    552             if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
    553             {
    554                 if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
    555                 if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
    556             }
    557 
    558         // Process initial action
    559         if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
    560         {
    561             if (mouse_button_clicked != -1 && g.ActiveId != id)
    562             {
    563                 if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
    564                     SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);
    565                 if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
    566                 {
    567                     SetActiveID(id, window);
    568                     g.ActiveIdMouseButton = mouse_button_clicked;
    569                     if (!(flags & ImGuiButtonFlags_NoNavFocus))
    570                     {
    571                         SetFocusID(id, window);
    572                         FocusWindow(window);
    573                     }
    574                     else
    575                     {
    576                         FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
    577                     }
    578                 }
    579                 if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
    580                 {
    581                     pressed = true;
    582                     if (flags & ImGuiButtonFlags_NoHoldingActiveId)
    583                         ClearActiveID();
    584                     else
    585                         SetActiveID(id, window); // Hold on ID
    586                     g.ActiveIdMouseButton = mouse_button_clicked;
    587                     if (!(flags & ImGuiButtonFlags_NoNavFocus))
    588                     {
    589                         SetFocusID(id, window);
    590                         FocusWindow(window);
    591                     }
    592                     else
    593                     {
    594                         FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
    595                     }
    596                 }
    597             }
    598             if (flags & ImGuiButtonFlags_PressedOnRelease)
    599             {
    600                 if (mouse_button_released != -1)
    601                 {
    602                     const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior
    603                     if (!has_repeated_at_least_once)
    604                         pressed = true;
    605                     if (!(flags & ImGuiButtonFlags_NoNavFocus))
    606                         SetFocusID(id, window);
    607                     ClearActiveID();
    608                 }
    609             }
    610 
    611             // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
    612             // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
    613             if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))
    614                 if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id))
    615                     pressed = true;
    616         }
    617 
    618         if (pressed)
    619             g.NavDisableHighlight = true;
    620     }
    621 
    622     // Gamepad/Keyboard handling
    623     // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.
    624     if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover)
    625         if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
    626             hovered = true;
    627     if (g.NavActivateDownId == id)
    628     {
    629         bool nav_activated_by_code = (g.NavActivateId == id);
    630         bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
    631         if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
    632         {
    633             // Avoid pressing multiple keys from triggering excessive amount of repeat events
    634             const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
    635             const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
    636             const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
    637             const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);
    638             nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
    639         }
    640         if (nav_activated_by_code || nav_activated_by_inputs)
    641         {
    642             // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
    643             pressed = true;
    644             SetActiveID(id, window);
    645             g.ActiveIdSource = g.NavInputSource;
    646             if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
    647                 SetFocusID(id, window);
    648             if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
    649                 g.ActiveIdFromShortcut = true;
    650         }
    651     }
    652 
    653     // Process while held
    654     bool held = false;
    655     if (g.ActiveId == id)
    656     {
    657         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
    658         {
    659             if (g.ActiveIdIsJustActivated)
    660                 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
    661 
    662             const int mouse_button = g.ActiveIdMouseButton;
    663             if (mouse_button == -1)
    664             {
    665                 // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).
    666                 ClearActiveID();
    667             }
    668             else if (IsMouseDown(mouse_button, test_owner_id))
    669             {
    670                 held = true;
    671             }
    672             else
    673             {
    674                 bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
    675                 bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
    676                 if ((release_in || release_anywhere) && !g.DragDropActive)
    677                 {
    678                     // Report as pressed when releasing the mouse (this is the most common path)
    679                     bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;
    680                     bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
    681                     bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);
    682                     if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
    683                         pressed = true;
    684                 }
    685                 ClearActiveID();
    686             }
    687             if (!(flags & ImGuiButtonFlags_NoNavFocus))
    688                 g.NavDisableHighlight = true;
    689         }
    690         else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
    691         {
    692             // When activated using Nav, we hold on the ActiveID until activation button is released
    693             if (g.NavActivateDownId == id)
    694                 held = true; // hovered == true not true as we are already likely hovered on direct activation.
    695             else
    696                 ClearActiveID();
    697         }
    698         if (pressed)
    699             g.ActiveIdHasBeenPressedBefore = true;
    700     }
    701 
    702     // Activation highlight (this may be a remote activation)
    703     if (g.NavHighlightActivatedId == id)
    704         hovered = true;
    705 
    706     if (out_hovered) *out_hovered = hovered;
    707     if (out_held) *out_held = held;
    708 
    709     return pressed;
    710 }
    711 
    712 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
    713 {
    714     ImGuiWindow* window = GetCurrentWindow();
    715     if (window->SkipItems)
    716         return false;
    717 
    718     ImGuiContext& g = *GImGui;
    719     const ImGuiStyle& style = g.Style;
    720     const ImGuiID id = window->GetID(label);
    721     const ImVec2 label_size = CalcTextSize(label, NULL, true);
    722 
    723     ImVec2 pos = window->DC.CursorPos;
    724     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)
    725         pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
    726     ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
    727 
    728     const ImRect bb(pos, pos + size);
    729     ItemSize(size, style.FramePadding.y);
    730     if (!ItemAdd(bb, id))
    731         return false;
    732 
    733     bool hovered, held;
    734     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
    735 
    736     // Render
    737     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    738     RenderNavHighlight(bb, id);
    739     RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
    740 
    741     if (g.LogEnabled)
    742         LogSetNextTextDecoration("[", "]");
    743     RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
    744 
    745     // Automatically close popups
    746     //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
    747     //    CloseCurrentPopup();
    748 
    749     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
    750     return pressed;
    751 }
    752 
    753 bool ImGui::Button(const char* label, const ImVec2& size_arg)
    754 {
    755     return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
    756 }
    757 
    758 // Small buttons fits within text without additional vertical spacing.
    759 bool ImGui::SmallButton(const char* label)
    760 {
    761     ImGuiContext& g = *GImGui;
    762     float backup_padding_y = g.Style.FramePadding.y;
    763     g.Style.FramePadding.y = 0.0f;
    764     bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
    765     g.Style.FramePadding.y = backup_padding_y;
    766     return pressed;
    767 }
    768 
    769 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
    770 // 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)
    771 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
    772 {
    773     ImGuiContext& g = *GImGui;
    774     ImGuiWindow* window = GetCurrentWindow();
    775     if (window->SkipItems)
    776         return false;
    777 
    778     // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
    779     IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
    780 
    781     const ImGuiID id = window->GetID(str_id);
    782     ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
    783     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    784     ItemSize(size);
    785     if (!ItemAdd(bb, id))
    786         return false;
    787 
    788     bool hovered, held;
    789     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
    790 
    791     IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
    792     return pressed;
    793 }
    794 
    795 bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
    796 {
    797     ImGuiContext& g = *GImGui;
    798     ImGuiWindow* window = GetCurrentWindow();
    799     if (window->SkipItems)
    800         return false;
    801 
    802     const ImGuiID id = window->GetID(str_id);
    803     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    804     const float default_size = GetFrameHeight();
    805     ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
    806     if (!ItemAdd(bb, id))
    807         return false;
    808 
    809     bool hovered, held;
    810     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
    811 
    812     // Render
    813     const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    814     const ImU32 text_col = GetColorU32(ImGuiCol_Text);
    815     RenderNavHighlight(bb, id);
    816     RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
    817     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);
    818 
    819     IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
    820     return pressed;
    821 }
    822 
    823 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
    824 {
    825     float sz = GetFrameHeight();
    826     return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
    827 }
    828 
    829 // Button to close a window
    830 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
    831 {
    832     ImGuiContext& g = *GImGui;
    833     ImGuiWindow* window = g.CurrentWindow;
    834 
    835     // 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)
    836     // 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?
    837     const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
    838     ImRect bb_interact = bb;
    839     const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
    840     if (area_to_visible_ratio < 1.5f)
    841         bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f));
    842 
    843     // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
    844     // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer).
    845     bool is_clipped = !ItemAdd(bb_interact, id);
    846 
    847     bool hovered, held;
    848     bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
    849     if (is_clipped)
    850         return pressed;
    851 
    852     // Render
    853     ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
    854     if (hovered)
    855         window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
    856     RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact);
    857     ImU32 cross_col = GetColorU32(ImGuiCol_Text);
    858     ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
    859     float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
    860     window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
    861     window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
    862 
    863     return pressed;
    864 }
    865 
    866 bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
    867 {
    868     ImGuiContext& g = *GImGui;
    869     ImGuiWindow* window = g.CurrentWindow;
    870 
    871     ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
    872     bool is_clipped = !ItemAdd(bb, id);
    873     bool hovered, held;
    874     bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
    875     if (is_clipped)
    876         return pressed;
    877 
    878     // Render
    879     ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    880     ImU32 text_col = GetColorU32(ImGuiCol_Text);
    881     if (hovered || held)
    882         window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
    883     RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact);
    884     RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
    885 
    886     // Switch to moving the window after mouse is moved beyond the initial drag threshold
    887     if (IsItemActive() && IsMouseDragging(0))
    888         StartMouseMovingWindow(window);
    889 
    890     return pressed;
    891 }
    892 
    893 ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
    894 {
    895     return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
    896 }
    897 
    898 // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
    899 ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
    900 {
    901     const ImRect outer_rect = window->Rect();
    902     const ImRect inner_rect = window->InnerRect;
    903     const float border_size = window->WindowBorderSize;
    904     const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
    905     IM_ASSERT(scrollbar_size > 0.0f);
    906     if (axis == ImGuiAxis_X)
    907         return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
    908     else
    909         return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);
    910 }
    911 
    912 void ImGui::Scrollbar(ImGuiAxis axis)
    913 {
    914     ImGuiContext& g = *GImGui;
    915     ImGuiWindow* window = g.CurrentWindow;
    916     const ImGuiID id = GetWindowScrollbarID(window, axis);
    917 
    918     // Calculate scrollbar bounding box
    919     ImRect bb = GetWindowScrollbarRect(window, axis);
    920     ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
    921     if (axis == ImGuiAxis_X)
    922     {
    923         rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
    924         if (!window->ScrollbarY)
    925             rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
    926     }
    927     else
    928     {
    929         if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
    930             rounding_corners |= ImDrawFlags_RoundCornersTopRight;
    931         if (!window->ScrollbarX)
    932             rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
    933     }
    934     float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
    935     float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
    936     ImS64 scroll = (ImS64)window->Scroll[axis];
    937     ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners);
    938     window->Scroll[axis] = (float)scroll;
    939 }
    940 
    941 // Vertical/Horizontal scrollbar
    942 // The entire piece of code below is rather confusing because:
    943 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
    944 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
    945 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
    946 // Still, the code should probably be made simpler..
    947 bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags)
    948 {
    949     ImGuiContext& g = *GImGui;
    950     ImGuiWindow* window = g.CurrentWindow;
    951     if (window->SkipItems)
    952         return false;
    953 
    954     const float bb_frame_width = bb_frame.GetWidth();
    955     const float bb_frame_height = bb_frame.GetHeight();
    956     if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
    957         return false;
    958 
    959     // 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)
    960     float alpha = 1.0f;
    961     if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
    962         alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
    963     if (alpha <= 0.0f)
    964         return false;
    965 
    966     const ImGuiStyle& style = g.Style;
    967     const bool allow_interaction = (alpha >= 1.0f);
    968 
    969     ImRect bb = bb_frame;
    970     bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
    971 
    972     // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
    973     const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
    974 
    975     // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
    976     // But we maintain a minimum size in pixel to allow for the user to still aim inside.
    977     IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
    978     const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
    979     const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v);
    980     const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
    981 
    982     // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
    983     bool held = false;
    984     bool hovered = false;
    985     ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);
    986     ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
    987 
    988     const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v);
    989     float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
    990     float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
    991     if (held && allow_interaction && grab_h_norm < 1.0f)
    992     {
    993         const float scrollbar_pos_v = bb.Min[axis];
    994         const float mouse_pos_v = g.IO.MousePos[axis];
    995 
    996         // Click position in scrollbar normalized space (0.0f->1.0f)
    997         const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
    998 
    999         const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0;
   1000         if (g.ActiveIdIsJustActivated)
   1001         {
   1002             // On initial click calculate the distance between mouse and the center of the grab
   1003             g.ScrollbarSeekMode = (short)held_dir;
   1004             g.ScrollbarClickDeltaToGrabCenter = (g.ScrollbarSeekMode == 0.0f) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f;
   1005         }
   1006 
   1007         // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
   1008         // 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
   1009         if (g.ScrollbarSeekMode == 0)
   1010         {
   1011             // Absolute seeking
   1012             const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
   1013             *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
   1014         }
   1015         else
   1016         {
   1017             // Page by page
   1018             if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode)
   1019             {
   1020                 float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f;
   1021                 *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max);
   1022             }
   1023         }
   1024 
   1025         // Update values for rendering
   1026         scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
   1027         grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
   1028 
   1029         // Update distance to grab now that we have seek'ed and saturated
   1030         //if (seek_absolute)
   1031         //    g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
   1032     }
   1033 
   1034     // Render
   1035     const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
   1036     const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
   1037     window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
   1038     ImRect grab_rect;
   1039     if (axis == ImGuiAxis_X)
   1040         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);
   1041     else
   1042         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);
   1043     window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
   1044 
   1045     return held;
   1046 }
   1047 
   1048 // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
   1049 // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
   1050 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
   1051 {
   1052     ImGuiWindow* window = GetCurrentWindow();
   1053     if (window->SkipItems)
   1054         return;
   1055 
   1056     const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f;
   1057     const ImVec2 padding(border_size, border_size);
   1058     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
   1059     ItemSize(bb);
   1060     if (!ItemAdd(bb, 0))
   1061         return;
   1062 
   1063     // Render
   1064     if (border_size > 0.0f)
   1065         window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size);
   1066     window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
   1067 }
   1068 
   1069 // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
   1070 // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
   1071 bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
   1072 {
   1073     ImGuiContext& g = *GImGui;
   1074     ImGuiWindow* window = GetCurrentWindow();
   1075     if (window->SkipItems)
   1076         return false;
   1077 
   1078     const ImVec2 padding = g.Style.FramePadding;
   1079     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
   1080     ItemSize(bb);
   1081     if (!ItemAdd(bb, id))
   1082         return false;
   1083 
   1084     bool hovered, held;
   1085     bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
   1086 
   1087     // Render
   1088     const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
   1089     RenderNavHighlight(bb, id);
   1090     RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
   1091     if (bg_col.w > 0.0f)
   1092         window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
   1093     window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
   1094 
   1095     return pressed;
   1096 }
   1097 
   1098 // Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
   1099 bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
   1100 {
   1101     ImGuiContext& g = *GImGui;
   1102     ImGuiWindow* window = g.CurrentWindow;
   1103     if (window->SkipItems)
   1104         return false;
   1105 
   1106     return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col);
   1107 }
   1108 
   1109 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   1110 // Legacy API obsoleted in 1.89. Two differences with new ImageButton()
   1111 // - new ImageButton() requires an explicit 'const char* str_id'    Old ImageButton() used opaque imTextureId (created issue with: multiple buttons with same image, transient texture id values, opaque computation of ID)
   1112 // - new ImageButton() always use style.FramePadding                Old ImageButton() had an override argument.
   1113 // If you need to change padding with new ImageButton() you can use PushStyleVar(ImGuiStyleVar_FramePadding, value), consistent with other Button functions.
   1114 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)
   1115 {
   1116     ImGuiContext& g = *GImGui;
   1117     ImGuiWindow* window = g.CurrentWindow;
   1118     if (window->SkipItems)
   1119         return false;
   1120 
   1121     // Default to using texture ID as ID. User can still push string/integer prefixes.
   1122     PushID((void*)(intptr_t)user_texture_id);
   1123     const ImGuiID id = window->GetID("#image");
   1124     PopID();
   1125 
   1126     if (frame_padding >= 0)
   1127         PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));
   1128     bool ret = ImageButtonEx(id, user_texture_id, size, uv0, uv1, bg_col, tint_col);
   1129     if (frame_padding >= 0)
   1130         PopStyleVar();
   1131     return ret;
   1132 }
   1133 #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   1134 
   1135 bool ImGui::Checkbox(const char* label, bool* v)
   1136 {
   1137     ImGuiWindow* window = GetCurrentWindow();
   1138     if (window->SkipItems)
   1139         return false;
   1140 
   1141     ImGuiContext& g = *GImGui;
   1142     const ImGuiStyle& style = g.Style;
   1143     const ImGuiID id = window->GetID(label);
   1144     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   1145 
   1146     const float square_sz = GetFrameHeight();
   1147     const ImVec2 pos = window->DC.CursorPos;
   1148     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));
   1149     ItemSize(total_bb, style.FramePadding.y);
   1150     const bool is_visible = ItemAdd(total_bb, id);
   1151     const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0;
   1152     if (!is_visible)
   1153         if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support
   1154         {
   1155             IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
   1156             return false;
   1157         }
   1158 
   1159     // Range-Selection/Multi-selection support (header)
   1160     bool checked = *v;
   1161     if (is_multi_select)
   1162         MultiSelectItemHeader(id, &checked, NULL);
   1163 
   1164     bool hovered, held;
   1165     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
   1166 
   1167     // Range-Selection/Multi-selection support (footer)
   1168     if (is_multi_select)
   1169         MultiSelectItemFooter(id, &checked, &pressed);
   1170     else if (pressed)
   1171         checked = !checked;
   1172 
   1173     if (*v != checked)
   1174     {
   1175         *v = checked;
   1176         pressed = true; // return value
   1177         MarkItemEdited(id);
   1178     }
   1179 
   1180     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
   1181     const bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0;
   1182     if (is_visible)
   1183     {
   1184         RenderNavHighlight(total_bb, id);
   1185         RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
   1186         ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
   1187         if (mixed_value)
   1188         {
   1189             // Undocumented tristate/mixed/indeterminate checkbox (#2644)
   1190             // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
   1191             ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)));
   1192             window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
   1193         }
   1194         else if (*v)
   1195         {
   1196             const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
   1197             RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
   1198         }
   1199     }
   1200     const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
   1201     if (g.LogEnabled)
   1202         LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
   1203     if (is_visible && label_size.x > 0.0f)
   1204         RenderText(label_pos, label);
   1205 
   1206     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
   1207     return pressed;
   1208 }
   1209 
   1210 template<typename T>
   1211 bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
   1212 {
   1213     bool all_on = (*flags & flags_value) == flags_value;
   1214     bool any_on = (*flags & flags_value) != 0;
   1215     bool pressed;
   1216     if (!all_on && any_on)
   1217     {
   1218         ImGuiContext& g = *GImGui;
   1219         g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
   1220         pressed = Checkbox(label, &all_on);
   1221     }
   1222     else
   1223     {
   1224         pressed = Checkbox(label, &all_on);
   1225 
   1226     }
   1227     if (pressed)
   1228     {
   1229         if (all_on)
   1230             *flags |= flags_value;
   1231         else
   1232             *flags &= ~flags_value;
   1233     }
   1234     return pressed;
   1235 }
   1236 
   1237 bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
   1238 {
   1239     return CheckboxFlagsT(label, flags, flags_value);
   1240 }
   1241 
   1242 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
   1243 {
   1244     return CheckboxFlagsT(label, flags, flags_value);
   1245 }
   1246 
   1247 bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
   1248 {
   1249     return CheckboxFlagsT(label, flags, flags_value);
   1250 }
   1251 
   1252 bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
   1253 {
   1254     return CheckboxFlagsT(label, flags, flags_value);
   1255 }
   1256 
   1257 bool ImGui::RadioButton(const char* label, bool active)
   1258 {
   1259     ImGuiWindow* window = GetCurrentWindow();
   1260     if (window->SkipItems)
   1261         return false;
   1262 
   1263     ImGuiContext& g = *GImGui;
   1264     const ImGuiStyle& style = g.Style;
   1265     const ImGuiID id = window->GetID(label);
   1266     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   1267 
   1268     const float square_sz = GetFrameHeight();
   1269     const ImVec2 pos = window->DC.CursorPos;
   1270     const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
   1271     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));
   1272     ItemSize(total_bb, style.FramePadding.y);
   1273     if (!ItemAdd(total_bb, id))
   1274         return false;
   1275 
   1276     ImVec2 center = check_bb.GetCenter();
   1277     center.x = IM_ROUND(center.x);
   1278     center.y = IM_ROUND(center.y);
   1279     const float radius = (square_sz - 1.0f) * 0.5f;
   1280 
   1281     bool hovered, held;
   1282     bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
   1283     if (pressed)
   1284         MarkItemEdited(id);
   1285 
   1286     RenderNavHighlight(total_bb, id);
   1287     const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
   1288     window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment);
   1289     if (active)
   1290     {
   1291         const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
   1292         window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));
   1293     }
   1294 
   1295     if (style.FrameBorderSize > 0.0f)
   1296     {
   1297         window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize);
   1298         window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize);
   1299     }
   1300 
   1301     ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
   1302     if (g.LogEnabled)
   1303         LogRenderedText(&label_pos, active ? "(x)" : "( )");
   1304     if (label_size.x > 0.0f)
   1305         RenderText(label_pos, label);
   1306 
   1307     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
   1308     return pressed;
   1309 }
   1310 
   1311 // 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..
   1312 bool ImGui::RadioButton(const char* label, int* v, int v_button)
   1313 {
   1314     const bool pressed = RadioButton(label, *v == v_button);
   1315     if (pressed)
   1316         *v = v_button;
   1317     return pressed;
   1318 }
   1319 
   1320 // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
   1321 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
   1322 {
   1323     ImGuiWindow* window = GetCurrentWindow();
   1324     if (window->SkipItems)
   1325         return;
   1326 
   1327     ImGuiContext& g = *GImGui;
   1328     const ImGuiStyle& style = g.Style;
   1329 
   1330     ImVec2 pos = window->DC.CursorPos;
   1331     ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
   1332     ImRect bb(pos, pos + size);
   1333     ItemSize(size, style.FramePadding.y);
   1334     if (!ItemAdd(bb, 0))
   1335         return;
   1336 
   1337     // Fraction < 0.0f will display an indeterminate progress bar animation
   1338     // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works.
   1339     const bool is_indeterminate = (fraction < 0.0f);
   1340     if (!is_indeterminate)
   1341         fraction = ImSaturate(fraction);
   1342 
   1343     // Out of courtesy we accept a NaN fraction without crashing
   1344     float fill_n0 = 0.0f;
   1345     float fill_n1 = (fraction == fraction) ? fraction : 0.0f;
   1346 
   1347     if (is_indeterminate)
   1348     {
   1349         const float fill_width_n = 0.2f;
   1350         fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n;
   1351         fill_n1 = ImSaturate(fill_n0 + fill_width_n);
   1352         fill_n0 = ImSaturate(fill_n0);
   1353     }
   1354 
   1355     // Render
   1356     RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
   1357     bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
   1358     RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding);
   1359 
   1360     // Default displaying the fraction as percentage string, but user can override it
   1361     // Don't display text for indeterminate bars by default
   1362     char overlay_buf[32];
   1363     if (!is_indeterminate || overlay != NULL)
   1364     {
   1365         if (!overlay)
   1366         {
   1367             ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
   1368             overlay = overlay_buf;
   1369         }
   1370 
   1371         ImVec2 overlay_size = CalcTextSize(overlay, NULL);
   1372         if (overlay_size.x > 0.0f)
   1373         {
   1374             float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x;
   1375             RenderTextClipped(ImVec2(ImClamp(text_x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb);
   1376         }
   1377     }
   1378 }
   1379 
   1380 void ImGui::Bullet()
   1381 {
   1382     ImGuiWindow* window = GetCurrentWindow();
   1383     if (window->SkipItems)
   1384         return;
   1385 
   1386     ImGuiContext& g = *GImGui;
   1387     const ImGuiStyle& style = g.Style;
   1388     const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);
   1389     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
   1390     ItemSize(bb);
   1391     if (!ItemAdd(bb, 0))
   1392     {
   1393         SameLine(0, style.FramePadding.x * 2);
   1394         return;
   1395     }
   1396 
   1397     // Render and stay on same line
   1398     ImU32 text_col = GetColorU32(ImGuiCol_Text);
   1399     RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
   1400     SameLine(0, style.FramePadding.x * 2.0f);
   1401 }
   1402 
   1403 // This is provided as a convenience for being an often requested feature.
   1404 // FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system.
   1405 // Because of this we currently don't provide many styling options for this widget
   1406 // (e.g. hovered/active colors are automatically inferred from a single color).
   1407 bool ImGui::TextLink(const char* label)
   1408 {
   1409     ImGuiWindow* window = GetCurrentWindow();
   1410     if (window->SkipItems)
   1411         return false;
   1412 
   1413     ImGuiContext& g = *GImGui;
   1414     const ImGuiID id = window->GetID(label);
   1415     const char* label_end = FindRenderedTextEnd(label);
   1416 
   1417     ImVec2 pos = window->DC.CursorPos;
   1418     ImVec2 size = CalcTextSize(label, label_end, true);
   1419     ImRect bb(pos, pos + size);
   1420     ItemSize(size, 0.0f);
   1421     if (!ItemAdd(bb, id))
   1422         return false;
   1423 
   1424     bool hovered, held;
   1425     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
   1426     RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_None);
   1427 
   1428     ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink];
   1429     ImVec4 line_colf = text_colf;
   1430     {
   1431         // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets,
   1432         // as we are currently experimenting/planning a different styling system.
   1433         float h, s, v;
   1434         ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v);
   1435         if (held || hovered)
   1436         {
   1437             v = ImSaturate(v + (held ? 0.4f : 0.3f));
   1438             h = ImFmod(h + 0.02f, 1.0f);
   1439         }
   1440         ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z);
   1441         v = ImSaturate(v - 0.20f);
   1442         ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);
   1443     }
   1444 
   1445     float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f);
   1446     window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode.
   1447 
   1448     PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
   1449     RenderText(bb.Min, label, label_end);
   1450     PopStyleColor();
   1451 
   1452     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
   1453     return pressed;
   1454 }
   1455 
   1456 void ImGui::TextLinkOpenURL(const char* label, const char* url)
   1457 {
   1458     ImGuiContext& g = *GImGui;
   1459     if (url == NULL)
   1460         url = label;
   1461     if (TextLink(label))
   1462         if (g.IO.PlatformOpenInShellFn != NULL)
   1463             g.IO.PlatformOpenInShellFn(&g, url);
   1464     SetItemTooltip("%s", url); // It is more reassuring for user to _always_ display URL when we same as label
   1465     if (BeginPopupContextItem())
   1466     {
   1467         if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink)))
   1468             SetClipboardText(url);
   1469         EndPopup();
   1470     }
   1471 }
   1472 
   1473 //-------------------------------------------------------------------------
   1474 // [SECTION] Widgets: Low-level Layout helpers
   1475 //-------------------------------------------------------------------------
   1476 // - Spacing()
   1477 // - Dummy()
   1478 // - NewLine()
   1479 // - AlignTextToFramePadding()
   1480 // - SeparatorEx() [Internal]
   1481 // - Separator()
   1482 // - SplitterBehavior() [Internal]
   1483 // - ShrinkWidths() [Internal]
   1484 //-------------------------------------------------------------------------
   1485 
   1486 void ImGui::Spacing()
   1487 {
   1488     ImGuiWindow* window = GetCurrentWindow();
   1489     if (window->SkipItems)
   1490         return;
   1491     ItemSize(ImVec2(0, 0));
   1492 }
   1493 
   1494 void ImGui::Dummy(const ImVec2& size)
   1495 {
   1496     ImGuiWindow* window = GetCurrentWindow();
   1497     if (window->SkipItems)
   1498         return;
   1499 
   1500     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
   1501     ItemSize(size);
   1502     ItemAdd(bb, 0);
   1503 }
   1504 
   1505 void ImGui::NewLine()
   1506 {
   1507     ImGuiWindow* window = GetCurrentWindow();
   1508     if (window->SkipItems)
   1509         return;
   1510 
   1511     ImGuiContext& g = *GImGui;
   1512     const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
   1513     window->DC.LayoutType = ImGuiLayoutType_Vertical;
   1514     window->DC.IsSameLine = false;
   1515     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.
   1516         ItemSize(ImVec2(0, 0));
   1517     else
   1518         ItemSize(ImVec2(0.0f, g.FontSize));
   1519     window->DC.LayoutType = backup_layout_type;
   1520 }
   1521 
   1522 void ImGui::AlignTextToFramePadding()
   1523 {
   1524     ImGuiWindow* window = GetCurrentWindow();
   1525     if (window->SkipItems)
   1526         return;
   1527 
   1528     ImGuiContext& g = *GImGui;
   1529     window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
   1530     window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
   1531 }
   1532 
   1533 // Horizontal/vertical separating line
   1534 // FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
   1535 // Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.
   1536 void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
   1537 {
   1538     ImGuiWindow* window = GetCurrentWindow();
   1539     if (window->SkipItems)
   1540         return;
   1541 
   1542     ImGuiContext& g = *GImGui;
   1543     IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected
   1544     IM_ASSERT(thickness > 0.0f);
   1545 
   1546     if (flags & ImGuiSeparatorFlags_Vertical)
   1547     {
   1548         // Vertical separator, for menu bars (use current line height).
   1549         float y1 = window->DC.CursorPos.y;
   1550         float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
   1551         const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
   1552         ItemSize(ImVec2(thickness, 0.0f));
   1553         if (!ItemAdd(bb, 0))
   1554             return;
   1555 
   1556         // Draw
   1557         window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
   1558         if (g.LogEnabled)
   1559             LogText(" |");
   1560     }
   1561     else if (flags & ImGuiSeparatorFlags_Horizontal)
   1562     {
   1563         // Horizontal Separator
   1564         float x1 = window->DC.CursorPos.x;
   1565         float x2 = window->WorkRect.Max.x;
   1566 
   1567         // Preserve legacy behavior inside Columns()
   1568         // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
   1569         // We currently don't need to provide the same feature for tables because tables naturally have border features.
   1570         ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
   1571         if (columns)
   1572         {
   1573             x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03
   1574             x2 = window->Pos.x + window->Size.x;
   1575             PushColumnsBackground();
   1576         }
   1577 
   1578         // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
   1579         // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
   1580         const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change.
   1581         const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
   1582         ItemSize(ImVec2(0.0f, thickness_for_layout));
   1583 
   1584         if (ItemAdd(bb, 0))
   1585         {
   1586             // Draw
   1587             window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
   1588             if (g.LogEnabled)
   1589                 LogRenderedText(&bb.Min, "--------------------------------\n");
   1590 
   1591         }
   1592         if (columns)
   1593         {
   1594             PopColumnsBackground();
   1595             columns->LineMinY = window->DC.CursorPos.y;
   1596         }
   1597     }
   1598 }
   1599 
   1600 void ImGui::Separator()
   1601 {
   1602     ImGuiContext& g = *GImGui;
   1603     ImGuiWindow* window = g.CurrentWindow;
   1604     if (window->SkipItems)
   1605         return;
   1606 
   1607     // Those flags should eventually be configurable by the user
   1608     // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.
   1609     ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
   1610 
   1611     // Only applies to legacy Columns() api as they relied on Separator() a lot.
   1612     if (window->DC.CurrentColumns)
   1613         flags |= ImGuiSeparatorFlags_SpanAllColumns;
   1614 
   1615     SeparatorEx(flags, 1.0f);
   1616 }
   1617 
   1618 void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
   1619 {
   1620     ImGuiContext& g = *GImGui;
   1621     ImGuiWindow* window = g.CurrentWindow;
   1622     ImGuiStyle& style = g.Style;
   1623 
   1624     const ImVec2 label_size = CalcTextSize(label, label_end, false);
   1625     const ImVec2 pos = window->DC.CursorPos;
   1626     const ImVec2 padding = style.SeparatorTextPadding;
   1627 
   1628     const float separator_thickness = style.SeparatorTextBorderSize;
   1629     const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));
   1630     const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
   1631     const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f));
   1632     ItemSize(min_size, text_baseline_y);
   1633     if (!ItemAdd(bb, id))
   1634         return;
   1635 
   1636     const float sep1_x1 = pos.x;
   1637     const float sep2_x2 = bb.Max.x;
   1638     const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
   1639 
   1640     const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);
   1641     const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN
   1642 
   1643     // This allows using SameLine() to position something in the 'extra_w'
   1644     window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
   1645 
   1646     const ImU32 separator_col = GetColorU32(ImGuiCol_Separator);
   1647     if (label_size.x > 0.0f)
   1648     {
   1649         const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
   1650         const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
   1651         if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
   1652             window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness);
   1653         if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
   1654             window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
   1655         if (g.LogEnabled)
   1656             LogSetNextTextDecoration("---", NULL);
   1657         RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size);
   1658     }
   1659     else
   1660     {
   1661         if (g.LogEnabled)
   1662             LogText("---");
   1663         if (separator_thickness > 0.0f)
   1664             window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
   1665     }
   1666 }
   1667 
   1668 void ImGui::SeparatorText(const char* label)
   1669 {
   1670     ImGuiWindow* window = GetCurrentWindow();
   1671     if (window->SkipItems)
   1672         return;
   1673 
   1674     // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
   1675     // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
   1676     // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)
   1677     // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
   1678     // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
   1679     // and then we can turn this into a format function.
   1680     SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f);
   1681 }
   1682 
   1683 // 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.
   1684 bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay, ImU32 bg_col)
   1685 {
   1686     ImGuiContext& g = *GImGui;
   1687     ImGuiWindow* window = g.CurrentWindow;
   1688 
   1689     if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav))
   1690         return false;
   1691 
   1692     // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
   1693     // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
   1694     // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
   1695     ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
   1696 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   1697     button_flags |= ImGuiButtonFlags_AllowOverlap;
   1698 #endif
   1699 
   1700     bool hovered, held;
   1701     ImRect bb_interact = bb;
   1702     bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
   1703     ButtonBehavior(bb_interact, id, &hovered, &held, button_flags);
   1704     if (hovered)
   1705         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
   1706 
   1707     if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
   1708         SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
   1709 
   1710     ImRect bb_render = bb;
   1711     if (held)
   1712     {
   1713         float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
   1714 
   1715         // Minimum pane size
   1716         float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
   1717         float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
   1718         if (mouse_delta < -size_1_maximum_delta)
   1719             mouse_delta = -size_1_maximum_delta;
   1720         if (mouse_delta > size_2_maximum_delta)
   1721             mouse_delta = size_2_maximum_delta;
   1722 
   1723         // Apply resize
   1724         if (mouse_delta != 0.0f)
   1725         {
   1726             *size1 = ImMax(*size1 + mouse_delta, min_size1);
   1727             *size2 = ImMax(*size2 - mouse_delta, min_size2);
   1728             bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
   1729             MarkItemEdited(id);
   1730         }
   1731     }
   1732 
   1733     // Render at new position
   1734     if (bg_col & IM_COL32_A_MASK)
   1735         window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
   1736     const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
   1737     window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
   1738 
   1739     return held;
   1740 }
   1741 
   1742 static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
   1743 {
   1744     const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
   1745     const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
   1746     if (int d = (int)(b->Width - a->Width))
   1747         return d;
   1748     return (b->Index - a->Index);
   1749 }
   1750 
   1751 // Shrink excess width from a set of item, by removing width from the larger items first.
   1752 // Set items Width to -1.0f to disable shrinking this item.
   1753 void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
   1754 {
   1755     if (count == 1)
   1756     {
   1757         if (items[0].Width >= 0.0f)
   1758             items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
   1759         return;
   1760     }
   1761     ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
   1762     int count_same_width = 1;
   1763     while (width_excess > 0.0f && count_same_width < count)
   1764     {
   1765         while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
   1766             count_same_width++;
   1767         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);
   1768         if (max_width_to_remove_per_item <= 0.0f)
   1769             break;
   1770         float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
   1771         for (int item_n = 0; item_n < count_same_width; item_n++)
   1772             items[item_n].Width -= width_to_remove_per_item;
   1773         width_excess -= width_to_remove_per_item * count_same_width;
   1774     }
   1775 
   1776     // Round width and redistribute remainder
   1777     // 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.
   1778     width_excess = 0.0f;
   1779     for (int n = 0; n < count; n++)
   1780     {
   1781         float width_rounded = ImTrunc(items[n].Width);
   1782         width_excess += items[n].Width - width_rounded;
   1783         items[n].Width = width_rounded;
   1784     }
   1785     while (width_excess > 0.0f)
   1786         for (int n = 0; n < count && width_excess > 0.0f; n++)
   1787         {
   1788             float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
   1789             items[n].Width += width_to_add;
   1790             width_excess -= width_to_add;
   1791         }
   1792 }
   1793 
   1794 //-------------------------------------------------------------------------
   1795 // [SECTION] Widgets: ComboBox
   1796 //-------------------------------------------------------------------------
   1797 // - CalcMaxPopupHeightFromItemCount() [Internal]
   1798 // - BeginCombo()
   1799 // - BeginComboPopup() [Internal]
   1800 // - EndCombo()
   1801 // - BeginComboPreview() [Internal]
   1802 // - EndComboPreview() [Internal]
   1803 // - Combo()
   1804 //-------------------------------------------------------------------------
   1805 
   1806 static float CalcMaxPopupHeightFromItemCount(int items_count)
   1807 {
   1808     ImGuiContext& g = *GImGui;
   1809     if (items_count <= 0)
   1810         return FLT_MAX;
   1811     return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
   1812 }
   1813 
   1814 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
   1815 {
   1816     ImGuiContext& g = *GImGui;
   1817     ImGuiWindow* window = GetCurrentWindow();
   1818 
   1819     ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
   1820     g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
   1821     if (window->SkipItems)
   1822         return false;
   1823 
   1824     const ImGuiStyle& style = g.Style;
   1825     const ImGuiID id = window->GetID(label);
   1826     IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
   1827     if (flags & ImGuiComboFlags_WidthFitPreview)
   1828         IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
   1829 
   1830     const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
   1831     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   1832     const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f;
   1833     const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
   1834     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
   1835     const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
   1836     ItemSize(total_bb, style.FramePadding.y);
   1837     if (!ItemAdd(total_bb, id, &bb))
   1838         return false;
   1839 
   1840     // Open on click
   1841     bool hovered, held;
   1842     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
   1843     const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
   1844     bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
   1845     if (pressed && !popup_open)
   1846     {
   1847         OpenPopupEx(popup_id, ImGuiPopupFlags_None);
   1848         popup_open = true;
   1849     }
   1850 
   1851     // Render shape
   1852     const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   1853     const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
   1854     RenderNavHighlight(bb, id);
   1855     if (!(flags & ImGuiComboFlags_NoPreview))
   1856         window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
   1857     if (!(flags & ImGuiComboFlags_NoArrowButton))
   1858     {
   1859         ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
   1860         ImU32 text_col = GetColorU32(ImGuiCol_Text);
   1861         window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
   1862         if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
   1863             RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
   1864     }
   1865     RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
   1866 
   1867     // Custom preview
   1868     if (flags & ImGuiComboFlags_CustomPreview)
   1869     {
   1870         g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
   1871         IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
   1872         preview_value = NULL;
   1873     }
   1874 
   1875     // Render preview and label
   1876     if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
   1877     {
   1878         if (g.LogEnabled)
   1879             LogSetNextTextDecoration("{", "}");
   1880         RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
   1881     }
   1882     if (label_size.x > 0)
   1883         RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
   1884 
   1885     if (!popup_open)
   1886         return false;
   1887 
   1888     g.NextWindowData.Flags = backup_next_window_data_flags;
   1889     return BeginComboPopup(popup_id, bb, flags);
   1890 }
   1891 
   1892 bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
   1893 {
   1894     ImGuiContext& g = *GImGui;
   1895     if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
   1896     {
   1897         g.NextWindowData.ClearFlags();
   1898         return false;
   1899     }
   1900 
   1901     // Set popup size
   1902     float w = bb.GetWidth();
   1903     if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
   1904     {
   1905         g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
   1906     }
   1907     else
   1908     {
   1909         if ((flags & ImGuiComboFlags_HeightMask_) == 0)
   1910             flags |= ImGuiComboFlags_HeightRegular;
   1911         IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
   1912         int popup_max_height_in_items = -1;
   1913         if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
   1914         else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
   1915         else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
   1916         ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
   1917         if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
   1918             constraint_min.x = w;
   1919         if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
   1920             constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
   1921         SetNextWindowSizeConstraints(constraint_min, constraint_max);
   1922     }
   1923 
   1924     // This is essentially a specialized version of BeginPopupEx()
   1925     char name[16];
   1926     ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
   1927 
   1928     // Set position given a custom constraint (peak into expected window size so we can position it)
   1929     // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
   1930     // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
   1931     if (ImGuiWindow* popup_window = FindWindowByName(name))
   1932         if (popup_window->WasActive)
   1933         {
   1934             // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
   1935             ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
   1936             popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
   1937             ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
   1938             ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
   1939             SetNextWindowPos(pos);
   1940         }
   1941 
   1942     // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
   1943     ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
   1944     PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text
   1945     bool ret = Begin(name, NULL, window_flags);
   1946     PopStyleVar();
   1947     if (!ret)
   1948     {
   1949         EndPopup();
   1950         IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
   1951         return false;
   1952     }
   1953     g.BeginComboDepth++;
   1954     return true;
   1955 }
   1956 
   1957 void ImGui::EndCombo()
   1958 {
   1959     ImGuiContext& g = *GImGui;
   1960     EndPopup();
   1961     g.BeginComboDepth--;
   1962 }
   1963 
   1964 // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
   1965 // (Experimental, see GitHub issues: #1658, #4168)
   1966 bool ImGui::BeginComboPreview()
   1967 {
   1968     ImGuiContext& g = *GImGui;
   1969     ImGuiWindow* window = g.CurrentWindow;
   1970     ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
   1971 
   1972     if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
   1973         return false;
   1974     IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag?
   1975     if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional)
   1976         return false;
   1977 
   1978     // FIXME: This could be contained in a PushWorkRect() api
   1979     preview_data->BackupCursorPos = window->DC.CursorPos;
   1980     preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
   1981     preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
   1982     preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
   1983     preview_data->BackupLayout = window->DC.LayoutType;
   1984     window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
   1985     window->DC.CursorMaxPos = window->DC.CursorPos;
   1986     window->DC.LayoutType = ImGuiLayoutType_Horizontal;
   1987     window->DC.IsSameLine = false;
   1988     PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
   1989 
   1990     return true;
   1991 }
   1992 
   1993 void ImGui::EndComboPreview()
   1994 {
   1995     ImGuiContext& g = *GImGui;
   1996     ImGuiWindow* window = g.CurrentWindow;
   1997     ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
   1998 
   1999     // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
   2000     ImDrawList* draw_list = window->DrawList;
   2001     if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
   2002         if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
   2003         {
   2004             draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
   2005             draw_list->_TryMergeDrawCmds();
   2006         }
   2007     PopClipRect();
   2008     window->DC.CursorPos = preview_data->BackupCursorPos;
   2009     window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
   2010     window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
   2011     window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
   2012     window->DC.LayoutType = preview_data->BackupLayout;
   2013     window->DC.IsSameLine = false;
   2014     preview_data->PreviewRect = ImRect();
   2015 }
   2016 
   2017 // Getter for the old Combo() API: const char*[]
   2018 static const char* Items_ArrayGetter(void* data, int idx)
   2019 {
   2020     const char* const* items = (const char* const*)data;
   2021     return items[idx];
   2022 }
   2023 
   2024 // Getter for the old Combo() API: "item1\0item2\0item3\0"
   2025 static const char* Items_SingleStringGetter(void* data, int idx)
   2026 {
   2027     const char* items_separated_by_zeros = (const char*)data;
   2028     int items_count = 0;
   2029     const char* p = items_separated_by_zeros;
   2030     while (*p)
   2031     {
   2032         if (idx == items_count)
   2033             break;
   2034         p += strlen(p) + 1;
   2035         items_count++;
   2036     }
   2037     return *p ? p : NULL;
   2038 }
   2039 
   2040 // Old API, prefer using BeginCombo() nowadays if you can.
   2041 bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items)
   2042 {
   2043     ImGuiContext& g = *GImGui;
   2044 
   2045     // Call the getter to obtain the preview string which is a parameter to BeginCombo()
   2046     const char* preview_value = NULL;
   2047     if (*current_item >= 0 && *current_item < items_count)
   2048         preview_value = getter(user_data, *current_item);
   2049 
   2050     // 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.
   2051     if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
   2052         SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
   2053 
   2054     if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
   2055         return false;
   2056 
   2057     // Display items
   2058     bool value_changed = false;
   2059     ImGuiListClipper clipper;
   2060     clipper.Begin(items_count);
   2061     clipper.IncludeItemByIndex(*current_item);
   2062     while (clipper.Step())
   2063         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
   2064         {
   2065             const char* item_text = getter(user_data, i);
   2066             if (item_text == NULL)
   2067                 item_text = "*Unknown item*";
   2068 
   2069             PushID(i);
   2070             const bool item_selected = (i == *current_item);
   2071             if (Selectable(item_text, item_selected) && *current_item != i)
   2072             {
   2073                 value_changed = true;
   2074                 *current_item = i;
   2075             }
   2076             if (item_selected)
   2077                 SetItemDefaultFocus();
   2078             PopID();
   2079         }
   2080 
   2081     EndCombo();
   2082     if (value_changed)
   2083         MarkItemEdited(g.LastItemData.ID);
   2084 
   2085     return value_changed;
   2086 }
   2087 
   2088 // Combo box helper allowing to pass an array of strings.
   2089 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
   2090 {
   2091     const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
   2092     return value_changed;
   2093 }
   2094 
   2095 // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
   2096 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
   2097 {
   2098     int items_count = 0;
   2099     const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
   2100     while (*p)
   2101     {
   2102         p += strlen(p) + 1;
   2103         items_count++;
   2104     }
   2105     bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
   2106     return value_changed;
   2107 }
   2108 
   2109 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
   2110 
   2111 struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };
   2112 static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)
   2113 {
   2114     ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;
   2115     const char* s = NULL;
   2116     data->OldCallback(data->UserData, idx, &s);
   2117     return s;
   2118 }
   2119 
   2120 bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items)
   2121 {
   2122     ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
   2123     return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items);
   2124 }
   2125 bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items)
   2126 {
   2127     ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
   2128     return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items);
   2129 }
   2130 
   2131 #endif
   2132 
   2133 //-------------------------------------------------------------------------
   2134 // [SECTION] Data Type and Data Formatting Helpers [Internal]
   2135 //-------------------------------------------------------------------------
   2136 // - DataTypeGetInfo()
   2137 // - DataTypeFormatString()
   2138 // - DataTypeApplyOp()
   2139 // - DataTypeApplyFromText()
   2140 // - DataTypeCompare()
   2141 // - DataTypeClamp()
   2142 // - GetMinimumStepAtDecimalPrecision
   2143 // - RoundScalarWithFormat<>()
   2144 //-------------------------------------------------------------------------
   2145 
   2146 static const ImGuiDataTypeInfo GDataTypeInfo[] =
   2147 {
   2148     { sizeof(char),             "S8",   "%d",   "%d"    },  // ImGuiDataType_S8
   2149     { sizeof(unsigned char),    "U8",   "%u",   "%u"    },
   2150     { sizeof(short),            "S16",  "%d",   "%d"    },  // ImGuiDataType_S16
   2151     { sizeof(unsigned short),   "U16",  "%u",   "%u"    },
   2152     { sizeof(int),              "S32",  "%d",   "%d"    },  // ImGuiDataType_S32
   2153     { sizeof(unsigned int),     "U32",  "%u",   "%u"    },
   2154 #ifdef _MSC_VER
   2155     { sizeof(ImS64),            "S64",  "%I64d","%I64d" },  // ImGuiDataType_S64
   2156     { sizeof(ImU64),            "U64",  "%I64u","%I64u" },
   2157 #else
   2158     { sizeof(ImS64),            "S64",  "%lld", "%lld"  },  // ImGuiDataType_S64
   2159     { sizeof(ImU64),            "U64",  "%llu", "%llu"  },
   2160 #endif
   2161     { sizeof(float),            "float", "%.3f","%f"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)
   2162     { sizeof(double),           "double","%f",  "%lf"   },  // ImGuiDataType_Double
   2163     { sizeof(bool),             "bool", "%d",   "%d"    },  // ImGuiDataType_Bool
   2164 };
   2165 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
   2166 
   2167 const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
   2168 {
   2169     IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
   2170     return &GDataTypeInfo[data_type];
   2171 }
   2172 
   2173 int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
   2174 {
   2175     // Signedness doesn't matter when pushing integer arguments
   2176     if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
   2177         return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
   2178     if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
   2179         return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
   2180     if (data_type == ImGuiDataType_Float)
   2181         return ImFormatString(buf, buf_size, format, *(const float*)p_data);
   2182     if (data_type == ImGuiDataType_Double)
   2183         return ImFormatString(buf, buf_size, format, *(const double*)p_data);
   2184     if (data_type == ImGuiDataType_S8)
   2185         return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
   2186     if (data_type == ImGuiDataType_U8)
   2187         return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
   2188     if (data_type == ImGuiDataType_S16)
   2189         return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
   2190     if (data_type == ImGuiDataType_U16)
   2191         return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
   2192     IM_ASSERT(0);
   2193     return 0;
   2194 }
   2195 
   2196 void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
   2197 {
   2198     IM_ASSERT(op == '+' || op == '-');
   2199     switch (data_type)
   2200     {
   2201         case ImGuiDataType_S8:
   2202             if (op == '+') { *(ImS8*)output  = ImAddClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
   2203             if (op == '-') { *(ImS8*)output  = ImSubClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
   2204             return;
   2205         case ImGuiDataType_U8:
   2206             if (op == '+') { *(ImU8*)output  = ImAddClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
   2207             if (op == '-') { *(ImU8*)output  = ImSubClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
   2208             return;
   2209         case ImGuiDataType_S16:
   2210             if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
   2211             if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
   2212             return;
   2213         case ImGuiDataType_U16:
   2214             if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
   2215             if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
   2216             return;
   2217         case ImGuiDataType_S32:
   2218             if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
   2219             if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
   2220             return;
   2221         case ImGuiDataType_U32:
   2222             if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
   2223             if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
   2224             return;
   2225         case ImGuiDataType_S64:
   2226             if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
   2227             if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
   2228             return;
   2229         case ImGuiDataType_U64:
   2230             if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
   2231             if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
   2232             return;
   2233         case ImGuiDataType_Float:
   2234             if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
   2235             if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
   2236             return;
   2237         case ImGuiDataType_Double:
   2238             if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
   2239             if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
   2240             return;
   2241         case ImGuiDataType_COUNT: break;
   2242     }
   2243     IM_ASSERT(0);
   2244 }
   2245 
   2246 // User can input math operators (e.g. +100) to edit a numerical values.
   2247 // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
   2248 bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty)
   2249 {
   2250     // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
   2251     const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
   2252     ImGuiDataTypeStorage data_backup;
   2253     memcpy(&data_backup, p_data, type_info->Size);
   2254 
   2255     while (ImCharIsBlankA(*buf))
   2256         buf++;
   2257     if (!buf[0])
   2258     {
   2259         if (p_data_when_empty != NULL)
   2260         {
   2261             memcpy(p_data, p_data_when_empty, type_info->Size);
   2262             return memcmp(&data_backup, p_data, type_info->Size) != 0;
   2263         }
   2264         return false;
   2265     }
   2266 
   2267     // Sanitize format
   2268     // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
   2269     // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.
   2270     char format_sanitized[32];
   2271     if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
   2272         format = type_info->ScanFmt;
   2273     else
   2274         format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
   2275 
   2276     // Small types need a 32-bit buffer to receive the result from scanf()
   2277     int v32 = 0;
   2278     if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
   2279         return false;
   2280     if (type_info->Size < 4)
   2281     {
   2282         if (data_type == ImGuiDataType_S8)
   2283             *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
   2284         else if (data_type == ImGuiDataType_U8)
   2285             *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
   2286         else if (data_type == ImGuiDataType_S16)
   2287             *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
   2288         else if (data_type == ImGuiDataType_U16)
   2289             *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
   2290         else
   2291             IM_ASSERT(0);
   2292     }
   2293 
   2294     return memcmp(&data_backup, p_data, type_info->Size) != 0;
   2295 }
   2296 
   2297 template<typename T>
   2298 static int DataTypeCompareT(const T* lhs, const T* rhs)
   2299 {
   2300     if (*lhs < *rhs) return -1;
   2301     if (*lhs > *rhs) return +1;
   2302     return 0;
   2303 }
   2304 
   2305 int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
   2306 {
   2307     switch (data_type)
   2308     {
   2309     case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);
   2310     case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);
   2311     case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
   2312     case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
   2313     case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
   2314     case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
   2315     case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
   2316     case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
   2317     case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
   2318     case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
   2319     case ImGuiDataType_COUNT:  break;
   2320     }
   2321     IM_ASSERT(0);
   2322     return 0;
   2323 }
   2324 
   2325 template<typename T>
   2326 static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
   2327 {
   2328     // Clamp, both sides are optional, return true if modified
   2329     if (v_min && *v < *v_min) { *v = *v_min; return true; }
   2330     if (v_max && *v > *v_max) { *v = *v_max; return true; }
   2331     return false;
   2332 }
   2333 
   2334 bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
   2335 {
   2336     switch (data_type)
   2337     {
   2338     case ImGuiDataType_S8:     return DataTypeClampT<ImS8  >((ImS8*  )p_data, (const ImS8*  )p_min, (const ImS8*  )p_max);
   2339     case ImGuiDataType_U8:     return DataTypeClampT<ImU8  >((ImU8*  )p_data, (const ImU8*  )p_min, (const ImU8*  )p_max);
   2340     case ImGuiDataType_S16:    return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
   2341     case ImGuiDataType_U16:    return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
   2342     case ImGuiDataType_S32:    return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
   2343     case ImGuiDataType_U32:    return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
   2344     case ImGuiDataType_S64:    return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
   2345     case ImGuiDataType_U64:    return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
   2346     case ImGuiDataType_Float:  return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
   2347     case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
   2348     case ImGuiDataType_COUNT:  break;
   2349     }
   2350     IM_ASSERT(0);
   2351     return false;
   2352 }
   2353 
   2354 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
   2355 {
   2356     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 };
   2357     if (decimal_precision < 0)
   2358         return FLT_MIN;
   2359     return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
   2360 }
   2361 
   2362 template<typename TYPE>
   2363 TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
   2364 {
   2365     IM_UNUSED(data_type);
   2366     IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
   2367     const char* fmt_start = ImParseFormatFindStart(format);
   2368     if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
   2369         return v;
   2370 
   2371     // Sanitize format
   2372     char fmt_sanitized[32];
   2373     ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
   2374     fmt_start = fmt_sanitized;
   2375 
   2376     // Format value with our rounding, and read back
   2377     char v_str[64];
   2378     ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
   2379     const char* p = v_str;
   2380     while (*p == ' ')
   2381         p++;
   2382     v = (TYPE)ImAtof(p);
   2383 
   2384     return v;
   2385 }
   2386 
   2387 //-------------------------------------------------------------------------
   2388 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
   2389 //-------------------------------------------------------------------------
   2390 // - DragBehaviorT<>() [Internal]
   2391 // - DragBehavior() [Internal]
   2392 // - DragScalar()
   2393 // - DragScalarN()
   2394 // - DragFloat()
   2395 // - DragFloat2()
   2396 // - DragFloat3()
   2397 // - DragFloat4()
   2398 // - DragFloatRange2()
   2399 // - DragInt()
   2400 // - DragInt2()
   2401 // - DragInt3()
   2402 // - DragInt4()
   2403 // - DragIntRange2()
   2404 //-------------------------------------------------------------------------
   2405 
   2406 // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
   2407 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2408 bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
   2409 {
   2410     ImGuiContext& g = *GImGui;
   2411     const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
   2412     const bool is_bounded = (v_min < v_max);
   2413     const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround);
   2414     const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
   2415     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
   2416 
   2417     // Default tweak speed
   2418     if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX))
   2419         v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
   2420 
   2421     // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
   2422     float adjust_delta = 0.0f;
   2423     if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
   2424     {
   2425         adjust_delta = g.IO.MouseDelta[axis];
   2426         if (g.IO.KeyAlt)
   2427             adjust_delta *= 1.0f / 100.0f;
   2428         if (g.IO.KeyShift)
   2429             adjust_delta *= 10.0f;
   2430     }
   2431     else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
   2432     {
   2433         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
   2434         const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
   2435         const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
   2436         const float tweak_factor = tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f;
   2437         adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
   2438         v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
   2439     }
   2440     adjust_delta *= v_speed;
   2441 
   2442     // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
   2443     if (axis == ImGuiAxis_Y)
   2444         adjust_delta = -adjust_delta;
   2445 
   2446     // For logarithmic use our range is effectively 0..1 so scale the delta into that range
   2447     if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
   2448         adjust_delta /= (float)(v_max - v_min);
   2449 
   2450     // Clear current value on activation
   2451     // 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.
   2452     const bool is_just_activated = g.ActiveIdIsJustActivated;
   2453     const bool is_already_past_limits_and_pushing_outward = is_bounded && !is_wrapped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
   2454     if (is_just_activated || is_already_past_limits_and_pushing_outward)
   2455     {
   2456         g.DragCurrentAccum = 0.0f;
   2457         g.DragCurrentAccumDirty = false;
   2458     }
   2459     else if (adjust_delta != 0.0f)
   2460     {
   2461         g.DragCurrentAccum += adjust_delta;
   2462         g.DragCurrentAccumDirty = true;
   2463     }
   2464 
   2465     if (!g.DragCurrentAccumDirty)
   2466         return false;
   2467 
   2468     TYPE v_cur = *v;
   2469     FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
   2470 
   2471     float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
   2472     const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
   2473     if (is_logarithmic)
   2474     {
   2475         // 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.
   2476         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
   2477         logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
   2478 
   2479         // Convert to parametric space, apply delta, convert back
   2480         float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2481         float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
   2482         v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2483         v_old_ref_for_accum_remainder = v_old_parametric;
   2484     }
   2485     else
   2486     {
   2487         v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
   2488     }
   2489 
   2490     // Round to user desired precision based on format string
   2491     if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
   2492         v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
   2493 
   2494     // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
   2495     g.DragCurrentAccumDirty = false;
   2496     if (is_logarithmic)
   2497     {
   2498         // Convert to parametric space, apply delta, convert back
   2499         float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   2500         g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
   2501     }
   2502     else
   2503     {
   2504         g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
   2505     }
   2506 
   2507     // Lose zero sign for float/double
   2508     if (v_cur == (TYPE)-0)
   2509         v_cur = (TYPE)0;
   2510 
   2511     if (*v != v_cur && is_bounded)
   2512     {
   2513         if (is_wrapped)
   2514         {
   2515             // Wrap values
   2516             if (v_cur < v_min)
   2517                 v_cur += v_max - v_min + (is_floating_point ? 0 : 1);
   2518             if (v_cur > v_max)
   2519                 v_cur -= v_max - v_min + (is_floating_point ? 0 : 1);
   2520         }
   2521         else
   2522         {
   2523             // Clamp values + handle overflow/wrap-around for integer types.
   2524             if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
   2525                 v_cur = v_min;
   2526             if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
   2527                 v_cur = v_max;
   2528         }
   2529     }
   2530 
   2531     // Apply result
   2532     if (*v == v_cur)
   2533         return false;
   2534     *v = v_cur;
   2535     return true;
   2536 }
   2537 
   2538 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)
   2539 {
   2540     // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
   2541     IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
   2542 
   2543     ImGuiContext& g = *GImGui;
   2544     if (g.ActiveId == id)
   2545     {
   2546         // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
   2547         if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
   2548             ClearActiveID();
   2549         else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
   2550             ClearActiveID();
   2551     }
   2552     if (g.ActiveId != id)
   2553         return false;
   2554     if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
   2555         return false;
   2556 
   2557     switch (data_type)
   2558     {
   2559     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; }
   2560     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; }
   2561     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; }
   2562     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; }
   2563     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);
   2564     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);
   2565     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);
   2566     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);
   2567     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);
   2568     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);
   2569     case ImGuiDataType_COUNT:  break;
   2570     }
   2571     IM_ASSERT(0);
   2572     return false;
   2573 }
   2574 
   2575 // 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.
   2576 // Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
   2577 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)
   2578 {
   2579     ImGuiWindow* window = GetCurrentWindow();
   2580     if (window->SkipItems)
   2581         return false;
   2582 
   2583     ImGuiContext& g = *GImGui;
   2584     const ImGuiStyle& style = g.Style;
   2585     const ImGuiID id = window->GetID(label);
   2586     const float w = CalcItemWidth();
   2587 
   2588     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   2589     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
   2590     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));
   2591 
   2592     const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
   2593     ItemSize(total_bb, style.FramePadding.y);
   2594     if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
   2595         return false;
   2596 
   2597     // Default format string when passing NULL
   2598     if (format == NULL)
   2599         format = DataTypeGetInfo(data_type)->PrintFmt;
   2600 
   2601     const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
   2602     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
   2603     if (!temp_input_is_active)
   2604     {
   2605         // Tabbing or CTRL-clicking on Drag turns it into an InputText
   2606         const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
   2607         const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
   2608         const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
   2609         if (make_active && (clicked || double_clicked))
   2610             SetKeyOwner(ImGuiKey_MouseLeft, id);
   2611         if (make_active && temp_input_allowed)
   2612             if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
   2613                 temp_input_is_active = true;
   2614 
   2615         // (Optional) simple click (without moving) turns Drag into an InputText
   2616         if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
   2617             if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
   2618             {
   2619                 g.NavActivateId = id;
   2620                 g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
   2621                 temp_input_is_active = true;
   2622             }
   2623 
   2624         if (make_active && !temp_input_is_active)
   2625         {
   2626             SetActiveID(id, window);
   2627             SetFocusID(id, window);
   2628             FocusWindow(window);
   2629             g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
   2630         }
   2631     }
   2632 
   2633     if (temp_input_is_active)
   2634     {
   2635         // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
   2636         const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0);
   2637         return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
   2638     }
   2639 
   2640     // Draw frame
   2641     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   2642     RenderNavHighlight(frame_bb, id);
   2643     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
   2644 
   2645     // Drag behavior
   2646     const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
   2647     if (value_changed)
   2648         MarkItemEdited(id);
   2649 
   2650     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
   2651     char value_buf[64];
   2652     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
   2653     if (g.LogEnabled)
   2654         LogSetNextTextDecoration("{", "}");
   2655     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
   2656 
   2657     if (label_size.x > 0.0f)
   2658         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   2659 
   2660     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
   2661     return value_changed;
   2662 }
   2663 
   2664 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)
   2665 {
   2666     ImGuiWindow* window = GetCurrentWindow();
   2667     if (window->SkipItems)
   2668         return false;
   2669 
   2670     ImGuiContext& g = *GImGui;
   2671     bool value_changed = false;
   2672     BeginGroup();
   2673     PushID(label);
   2674     PushMultiItemsWidths(components, CalcItemWidth());
   2675     size_t type_size = GDataTypeInfo[data_type].Size;
   2676     for (int i = 0; i < components; i++)
   2677     {
   2678         PushID(i);
   2679         if (i > 0)
   2680             SameLine(0, g.Style.ItemInnerSpacing.x);
   2681         value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
   2682         PopID();
   2683         PopItemWidth();
   2684         p_data = (void*)((char*)p_data + type_size);
   2685     }
   2686     PopID();
   2687 
   2688     const char* label_end = FindRenderedTextEnd(label);
   2689     if (label != label_end)
   2690     {
   2691         SameLine(0, g.Style.ItemInnerSpacing.x);
   2692         TextEx(label, label_end);
   2693     }
   2694 
   2695     EndGroup();
   2696     return value_changed;
   2697 }
   2698 
   2699 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2700 {
   2701     return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
   2702 }
   2703 
   2704 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2705 {
   2706     return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
   2707 }
   2708 
   2709 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2710 {
   2711     return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
   2712 }
   2713 
   2714 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   2715 {
   2716     return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
   2717 }
   2718 
   2719 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
   2720 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)
   2721 {
   2722     ImGuiWindow* window = GetCurrentWindow();
   2723     if (window->SkipItems)
   2724         return false;
   2725 
   2726     ImGuiContext& g = *GImGui;
   2727     PushID(label);
   2728     BeginGroup();
   2729     PushMultiItemsWidths(2, CalcItemWidth());
   2730 
   2731     float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
   2732     float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
   2733     ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2734     bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
   2735     PopItemWidth();
   2736     SameLine(0, g.Style.ItemInnerSpacing.x);
   2737 
   2738     float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
   2739     float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
   2740     ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2741     value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);
   2742     PopItemWidth();
   2743     SameLine(0, g.Style.ItemInnerSpacing.x);
   2744 
   2745     TextEx(label, FindRenderedTextEnd(label));
   2746     EndGroup();
   2747     PopID();
   2748 
   2749     return value_changed;
   2750 }
   2751 
   2752 // NB: v_speed is float to allow adjusting the drag speed with more precision
   2753 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2754 {
   2755     return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
   2756 }
   2757 
   2758 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2759 {
   2760     return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
   2761 }
   2762 
   2763 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2764 {
   2765     return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
   2766 }
   2767 
   2768 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   2769 {
   2770     return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
   2771 }
   2772 
   2773 // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
   2774 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)
   2775 {
   2776     ImGuiWindow* window = GetCurrentWindow();
   2777     if (window->SkipItems)
   2778         return false;
   2779 
   2780     ImGuiContext& g = *GImGui;
   2781     PushID(label);
   2782     BeginGroup();
   2783     PushMultiItemsWidths(2, CalcItemWidth());
   2784 
   2785     int min_min = (v_min >= v_max) ? INT_MIN : v_min;
   2786     int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
   2787     ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2788     bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
   2789     PopItemWidth();
   2790     SameLine(0, g.Style.ItemInnerSpacing.x);
   2791 
   2792     int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
   2793     int max_max = (v_min >= v_max) ? INT_MAX : v_max;
   2794     ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
   2795     value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
   2796     PopItemWidth();
   2797     SameLine(0, g.Style.ItemInnerSpacing.x);
   2798 
   2799     TextEx(label, FindRenderedTextEnd(label));
   2800     EndGroup();
   2801     PopID();
   2802 
   2803     return value_changed;
   2804 }
   2805 
   2806 //-------------------------------------------------------------------------
   2807 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
   2808 //-------------------------------------------------------------------------
   2809 // - ScaleRatioFromValueT<> [Internal]
   2810 // - ScaleValueFromRatioT<> [Internal]
   2811 // - SliderBehaviorT<>() [Internal]
   2812 // - SliderBehavior() [Internal]
   2813 // - SliderScalar()
   2814 // - SliderScalarN()
   2815 // - SliderFloat()
   2816 // - SliderFloat2()
   2817 // - SliderFloat3()
   2818 // - SliderFloat4()
   2819 // - SliderAngle()
   2820 // - SliderInt()
   2821 // - SliderInt2()
   2822 // - SliderInt3()
   2823 // - SliderInt4()
   2824 // - VSliderScalar()
   2825 // - VSliderFloat()
   2826 // - VSliderInt()
   2827 //-------------------------------------------------------------------------
   2828 
   2829 // Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
   2830 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2831 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)
   2832 {
   2833     if (v_min == v_max)
   2834         return 0.0f;
   2835     IM_UNUSED(data_type);
   2836 
   2837     const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
   2838     if (is_logarithmic)
   2839     {
   2840         bool flipped = v_max < v_min;
   2841 
   2842         if (flipped) // Handle the case where the range is backwards
   2843             ImSwap(v_min, v_max);
   2844 
   2845         // Fudge min/max to avoid getting close to log(0)
   2846         FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
   2847         FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
   2848 
   2849         // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
   2850         if ((v_min == 0.0f) && (v_max < 0.0f))
   2851             v_min_fudged = -logarithmic_zero_epsilon;
   2852         else if ((v_max == 0.0f) && (v_min < 0.0f))
   2853             v_max_fudged = -logarithmic_zero_epsilon;
   2854 
   2855         float result;
   2856         if (v_clamped <= v_min_fudged)
   2857             result = 0.0f; // Workaround for values that are in-range but below our fudge
   2858         else if (v_clamped >= v_max_fudged)
   2859             result = 1.0f; // Workaround for values that are in-range but above our fudge
   2860         else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
   2861         {
   2862             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)
   2863             float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
   2864             float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
   2865             if (v == 0.0f)
   2866                 result = zero_point_center; // Special case for exactly zero
   2867             else if (v < 0.0f)
   2868                 result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
   2869             else
   2870                 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));
   2871         }
   2872         else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
   2873             result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
   2874         else
   2875             result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
   2876 
   2877         return flipped ? (1.0f - result) : result;
   2878     }
   2879     else
   2880     {
   2881         // Linear slider
   2882         return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
   2883     }
   2884 }
   2885 
   2886 // Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
   2887 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2888 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)
   2889 {
   2890     // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
   2891     // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
   2892     if (t <= 0.0f || v_min == v_max)
   2893         return v_min;
   2894     if (t >= 1.0f)
   2895         return v_max;
   2896 
   2897     TYPE result = (TYPE)0;
   2898     if (is_logarithmic)
   2899     {
   2900         // Fudge min/max to avoid getting silly results close to zero
   2901         FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
   2902         FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
   2903 
   2904         const bool flipped = v_max < v_min; // Check if range is "backwards"
   2905         if (flipped)
   2906             ImSwap(v_min_fudged, v_max_fudged);
   2907 
   2908         // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
   2909         if ((v_max == 0.0f) && (v_min < 0.0f))
   2910             v_max_fudged = -logarithmic_zero_epsilon;
   2911 
   2912         float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
   2913 
   2914         if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
   2915         {
   2916             float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
   2917             float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
   2918             float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
   2919             if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
   2920                 result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
   2921             else if (t_with_flip < zero_point_center)
   2922                 result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
   2923             else
   2924                 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))));
   2925         }
   2926         else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
   2927             result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
   2928         else
   2929             result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
   2930     }
   2931     else
   2932     {
   2933         // Linear slider
   2934         const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
   2935         if (is_floating_point)
   2936         {
   2937             result = ImLerp(v_min, v_max, t);
   2938         }
   2939         else if (t < 1.0)
   2940         {
   2941             // - For integer values we want the clicking position to match the grab box so we round above
   2942             //   This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
   2943             // - 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
   2944             //   range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
   2945             FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
   2946             result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
   2947         }
   2948     }
   2949 
   2950     return result;
   2951 }
   2952 
   2953 // FIXME: Try to move more of the code into shared SliderBehavior()
   2954 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
   2955 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)
   2956 {
   2957     ImGuiContext& g = *GImGui;
   2958     const ImGuiStyle& style = g.Style;
   2959 
   2960     const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
   2961     const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
   2962     const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
   2963     const float v_range_f = (float)(v_min < v_max ? v_max - v_min : v_min - v_max); // We don't need high precision for what we do with it.
   2964 
   2965     // Calculate bounds
   2966     const float grab_padding = 2.0f; // FIXME: Should be part of style.
   2967     const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
   2968     float grab_sz = style.GrabMinSize;
   2969     if (!is_floating_point && v_range_f >= 0.0f)                         // v_range_f < 0 may happen on integer overflows
   2970         grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
   2971     grab_sz = ImMin(grab_sz, slider_sz);
   2972     const float slider_usable_sz = slider_sz - grab_sz;
   2973     const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
   2974     const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
   2975 
   2976     float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
   2977     float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
   2978     if (is_logarithmic)
   2979     {
   2980         // 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.
   2981         const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
   2982         logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
   2983         zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
   2984     }
   2985 
   2986     // Process interacting with the slider
   2987     bool value_changed = false;
   2988     if (g.ActiveId == id)
   2989     {
   2990         bool set_new_value = false;
   2991         float clicked_t = 0.0f;
   2992         if (g.ActiveIdSource == ImGuiInputSource_Mouse)
   2993         {
   2994             if (!g.IO.MouseDown[0])
   2995             {
   2996                 ClearActiveID();
   2997             }
   2998             else
   2999             {
   3000                 const float mouse_abs_pos = g.IO.MousePos[axis];
   3001                 if (g.ActiveIdIsJustActivated)
   3002                 {
   3003                     float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   3004                     if (axis == ImGuiAxis_Y)
   3005                         grab_t = 1.0f - grab_t;
   3006                     const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
   3007                     const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here.
   3008                     g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;
   3009                 }
   3010                 if (slider_usable_sz > 0.0f)
   3011                     clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);
   3012                 if (axis == ImGuiAxis_Y)
   3013                     clicked_t = 1.0f - clicked_t;
   3014                 set_new_value = true;
   3015             }
   3016         }
   3017         else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
   3018         {
   3019             if (g.ActiveIdIsJustActivated)
   3020             {
   3021                 g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
   3022                 g.SliderCurrentAccumDirty = false;
   3023             }
   3024 
   3025             float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
   3026             if (input_delta != 0.0f)
   3027             {
   3028                 const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
   3029                 const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
   3030                 const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
   3031                 if (decimal_precision > 0)
   3032                 {
   3033                     input_delta /= 100.0f;    // Gamepad/keyboard tweak speeds in % of slider bounds
   3034                     if (tweak_slow)
   3035                         input_delta /= 10.0f;
   3036                 }
   3037                 else
   3038                 {
   3039                     if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow)
   3040                         input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Gamepad/keyboard tweak speeds in integer steps
   3041                     else
   3042                         input_delta /= 100.0f;
   3043                 }
   3044                 if (tweak_fast)
   3045                     input_delta *= 10.0f;
   3046 
   3047                 g.SliderCurrentAccum += input_delta;
   3048                 g.SliderCurrentAccumDirty = true;
   3049             }
   3050 
   3051             float delta = g.SliderCurrentAccum;
   3052             if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
   3053             {
   3054                 ClearActiveID();
   3055             }
   3056             else if (g.SliderCurrentAccumDirty)
   3057             {
   3058                 clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   3059 
   3060                 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
   3061                 {
   3062                     set_new_value = false;
   3063                     g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
   3064                 }
   3065                 else
   3066                 {
   3067                     set_new_value = true;
   3068                     float old_clicked_t = clicked_t;
   3069                     clicked_t = ImSaturate(clicked_t + delta);
   3070 
   3071                     // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
   3072                     TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   3073                     if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
   3074                         v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
   3075                     float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   3076 
   3077                     if (delta > 0)
   3078                         g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
   3079                     else
   3080                         g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
   3081                 }
   3082 
   3083                 g.SliderCurrentAccumDirty = false;
   3084             }
   3085         }
   3086 
   3087         if (set_new_value)
   3088             if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
   3089                 set_new_value = false;
   3090 
   3091         if (set_new_value)
   3092         {
   3093             TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   3094 
   3095             // Round to user desired precision based on format string
   3096             if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
   3097                 v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
   3098 
   3099             // Apply result
   3100             if (*v != v_new)
   3101             {
   3102                 *v = v_new;
   3103                 value_changed = true;
   3104             }
   3105         }
   3106     }
   3107 
   3108     if (slider_sz < 1.0f)
   3109     {
   3110         *out_grab_bb = ImRect(bb.Min, bb.Min);
   3111     }
   3112     else
   3113     {
   3114         // Output grab position so it can be displayed by the caller
   3115         float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
   3116         if (axis == ImGuiAxis_Y)
   3117             grab_t = 1.0f - grab_t;
   3118         const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
   3119         if (axis == ImGuiAxis_X)
   3120             *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);
   3121         else
   3122             *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);
   3123     }
   3124 
   3125     return value_changed;
   3126 }
   3127 
   3128 // For 32-bit and larger types, slider bounds are limited to half the natural type range.
   3129 // 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.
   3130 // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
   3131 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)
   3132 {
   3133     // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
   3134     IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
   3135     IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX()
   3136 
   3137     switch (data_type)
   3138     {
   3139     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; }
   3140     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; }
   3141     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; }
   3142     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; }
   3143     case ImGuiDataType_S32:
   3144         IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
   3145         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);
   3146     case ImGuiDataType_U32:
   3147         IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
   3148         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);
   3149     case ImGuiDataType_S64:
   3150         IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
   3151         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);
   3152     case ImGuiDataType_U64:
   3153         IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
   3154         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);
   3155     case ImGuiDataType_Float:
   3156         IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
   3157         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);
   3158     case ImGuiDataType_Double:
   3159         IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
   3160         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);
   3161     case ImGuiDataType_COUNT: break;
   3162     }
   3163     IM_ASSERT(0);
   3164     return false;
   3165 }
   3166 
   3167 // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
   3168 // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
   3169 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)
   3170 {
   3171     ImGuiWindow* window = GetCurrentWindow();
   3172     if (window->SkipItems)
   3173         return false;
   3174 
   3175     ImGuiContext& g = *GImGui;
   3176     const ImGuiStyle& style = g.Style;
   3177     const ImGuiID id = window->GetID(label);
   3178     const float w = CalcItemWidth();
   3179 
   3180     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   3181     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
   3182     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));
   3183 
   3184     const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
   3185     ItemSize(total_bb, style.FramePadding.y);
   3186     if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
   3187         return false;
   3188 
   3189     // Default format string when passing NULL
   3190     if (format == NULL)
   3191         format = DataTypeGetInfo(data_type)->PrintFmt;
   3192 
   3193     const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
   3194     bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
   3195     if (!temp_input_is_active)
   3196     {
   3197         // Tabbing or CTRL-clicking on Slider turns it into an input box
   3198         const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
   3199         const bool make_active = (clicked || g.NavActivateId == id);
   3200         if (make_active && clicked)
   3201             SetKeyOwner(ImGuiKey_MouseLeft, id);
   3202         if (make_active && temp_input_allowed)
   3203             if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
   3204                 temp_input_is_active = true;
   3205 
   3206         if (make_active && !temp_input_is_active)
   3207         {
   3208             SetActiveID(id, window);
   3209             SetFocusID(id, window);
   3210             FocusWindow(window);
   3211             g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
   3212         }
   3213     }
   3214 
   3215     if (temp_input_is_active)
   3216     {
   3217         // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set
   3218         const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0;
   3219         return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
   3220     }
   3221 
   3222     // Draw frame
   3223     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   3224     RenderNavHighlight(frame_bb, id);
   3225     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
   3226 
   3227     // Slider behavior
   3228     ImRect grab_bb;
   3229     const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
   3230     if (value_changed)
   3231         MarkItemEdited(id);
   3232 
   3233     // Render grab
   3234     if (grab_bb.Max.x > grab_bb.Min.x)
   3235         window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
   3236 
   3237     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
   3238     char value_buf[64];
   3239     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
   3240     if (g.LogEnabled)
   3241         LogSetNextTextDecoration("{", "}");
   3242     RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
   3243 
   3244     if (label_size.x > 0.0f)
   3245         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   3246 
   3247     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
   3248     return value_changed;
   3249 }
   3250 
   3251 // Add multiple sliders on 1 line for compact edition of multiple components
   3252 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)
   3253 {
   3254     ImGuiWindow* window = GetCurrentWindow();
   3255     if (window->SkipItems)
   3256         return false;
   3257 
   3258     ImGuiContext& g = *GImGui;
   3259     bool value_changed = false;
   3260     BeginGroup();
   3261     PushID(label);
   3262     PushMultiItemsWidths(components, CalcItemWidth());
   3263     size_t type_size = GDataTypeInfo[data_type].Size;
   3264     for (int i = 0; i < components; i++)
   3265     {
   3266         PushID(i);
   3267         if (i > 0)
   3268             SameLine(0, g.Style.ItemInnerSpacing.x);
   3269         value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
   3270         PopID();
   3271         PopItemWidth();
   3272         v = (void*)((char*)v + type_size);
   3273     }
   3274     PopID();
   3275 
   3276     const char* label_end = FindRenderedTextEnd(label);
   3277     if (label != label_end)
   3278     {
   3279         SameLine(0, g.Style.ItemInnerSpacing.x);
   3280         TextEx(label, label_end);
   3281     }
   3282 
   3283     EndGroup();
   3284     return value_changed;
   3285 }
   3286 
   3287 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3288 {
   3289     return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
   3290 }
   3291 
   3292 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3293 {
   3294     return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
   3295 }
   3296 
   3297 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3298 {
   3299     return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
   3300 }
   3301 
   3302 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3303 {
   3304     return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
   3305 }
   3306 
   3307 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
   3308 {
   3309     if (format == NULL)
   3310         format = "%.0f deg";
   3311     float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
   3312     bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
   3313     *v_rad = v_deg * (2 * IM_PI) / 360.0f;
   3314     return value_changed;
   3315 }
   3316 
   3317 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3318 {
   3319     return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
   3320 }
   3321 
   3322 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3323 {
   3324     return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
   3325 }
   3326 
   3327 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3328 {
   3329     return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
   3330 }
   3331 
   3332 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3333 {
   3334     return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
   3335 }
   3336 
   3337 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)
   3338 {
   3339     ImGuiWindow* window = GetCurrentWindow();
   3340     if (window->SkipItems)
   3341         return false;
   3342 
   3343     ImGuiContext& g = *GImGui;
   3344     const ImGuiStyle& style = g.Style;
   3345     const ImGuiID id = window->GetID(label);
   3346 
   3347     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   3348     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
   3349     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));
   3350 
   3351     ItemSize(bb, style.FramePadding.y);
   3352     if (!ItemAdd(frame_bb, id))
   3353         return false;
   3354 
   3355     // Default format string when passing NULL
   3356     if (format == NULL)
   3357         format = DataTypeGetInfo(data_type)->PrintFmt;
   3358 
   3359     const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
   3360     const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
   3361     if (clicked || g.NavActivateId == id)
   3362     {
   3363         if (clicked)
   3364             SetKeyOwner(ImGuiKey_MouseLeft, id);
   3365         SetActiveID(id, window);
   3366         SetFocusID(id, window);
   3367         FocusWindow(window);
   3368         g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
   3369     }
   3370 
   3371     // Draw frame
   3372     const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
   3373     RenderNavHighlight(frame_bb, id);
   3374     RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
   3375 
   3376     // Slider behavior
   3377     ImRect grab_bb;
   3378     const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
   3379     if (value_changed)
   3380         MarkItemEdited(id);
   3381 
   3382     // Render grab
   3383     if (grab_bb.Max.y > grab_bb.Min.y)
   3384         window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
   3385 
   3386     // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
   3387     // For the vertical slider we allow centered text to overlap the frame padding
   3388     char value_buf[64];
   3389     const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
   3390     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));
   3391     if (label_size.x > 0.0f)
   3392         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   3393 
   3394     return value_changed;
   3395 }
   3396 
   3397 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
   3398 {
   3399     return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
   3400 }
   3401 
   3402 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
   3403 {
   3404     return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
   3405 }
   3406 
   3407 //-------------------------------------------------------------------------
   3408 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
   3409 //-------------------------------------------------------------------------
   3410 // - ImParseFormatFindStart() [Internal]
   3411 // - ImParseFormatFindEnd() [Internal]
   3412 // - ImParseFormatTrimDecorations() [Internal]
   3413 // - ImParseFormatSanitizeForPrinting() [Internal]
   3414 // - ImParseFormatSanitizeForScanning() [Internal]
   3415 // - ImParseFormatPrecision() [Internal]
   3416 // - TempInputTextScalar() [Internal]
   3417 // - InputScalar()
   3418 // - InputScalarN()
   3419 // - InputFloat()
   3420 // - InputFloat2()
   3421 // - InputFloat3()
   3422 // - InputFloat4()
   3423 // - InputInt()
   3424 // - InputInt2()
   3425 // - InputInt3()
   3426 // - InputInt4()
   3427 // - InputDouble()
   3428 //-------------------------------------------------------------------------
   3429 
   3430 // We don't use strchr() because our strings are usually very short and often start with '%'
   3431 const char* ImParseFormatFindStart(const char* fmt)
   3432 {
   3433     while (char c = fmt[0])
   3434     {
   3435         if (c == '%' && fmt[1] != '%')
   3436             return fmt;
   3437         else if (c == '%')
   3438             fmt++;
   3439         fmt++;
   3440     }
   3441     return fmt;
   3442 }
   3443 
   3444 const char* ImParseFormatFindEnd(const char* fmt)
   3445 {
   3446     // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
   3447     if (fmt[0] != '%')
   3448         return fmt;
   3449     const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
   3450     const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
   3451     for (char c; (c = *fmt) != 0; fmt++)
   3452     {
   3453         if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
   3454             return fmt + 1;
   3455         if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
   3456             return fmt + 1;
   3457     }
   3458     return fmt;
   3459 }
   3460 
   3461 // Extract the format out of a format string with leading or trailing decorations
   3462 //  fmt = "blah blah"  -> return ""
   3463 //  fmt = "%.3f"       -> return fmt
   3464 //  fmt = "hello %.3f" -> return fmt + 6
   3465 //  fmt = "%.3f hello" -> return buf written with "%.3f"
   3466 const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
   3467 {
   3468     const char* fmt_start = ImParseFormatFindStart(fmt);
   3469     if (fmt_start[0] != '%')
   3470         return "";
   3471     const char* fmt_end = ImParseFormatFindEnd(fmt_start);
   3472     if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
   3473         return fmt_start;
   3474     ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
   3475     return buf;
   3476 }
   3477 
   3478 // Sanitize format
   3479 // - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
   3480 // - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
   3481 void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
   3482 {
   3483     const char* fmt_end = ImParseFormatFindEnd(fmt_in);
   3484     IM_UNUSED(fmt_out_size);
   3485     IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
   3486     while (fmt_in < fmt_end)
   3487     {
   3488         char c = *fmt_in++;
   3489         if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
   3490             *(fmt_out++) = c;
   3491     }
   3492     *fmt_out = 0; // Zero-terminate
   3493 }
   3494 
   3495 // - For scanning we need to remove all width and precision fields and flags "%+3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d"
   3496 const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
   3497 {
   3498     const char* fmt_end = ImParseFormatFindEnd(fmt_in);
   3499     const char* fmt_out_begin = fmt_out;
   3500     IM_UNUSED(fmt_out_size);
   3501     IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
   3502     bool has_type = false;
   3503     while (fmt_in < fmt_end)
   3504     {
   3505         char c = *fmt_in++;
   3506         if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#'))
   3507             continue;
   3508         has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
   3509         if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
   3510             *(fmt_out++) = c;
   3511     }
   3512     *fmt_out = 0; // Zero-terminate
   3513     return fmt_out_begin;
   3514 }
   3515 
   3516 template<typename TYPE>
   3517 static const char* ImAtoi(const char* src, TYPE* output)
   3518 {
   3519     int negative = 0;
   3520     if (*src == '-') { negative = 1; src++; }
   3521     if (*src == '+') { src++; }
   3522     TYPE v = 0;
   3523     while (*src >= '0' && *src <= '9')
   3524         v = (v * 10) + (*src++ - '0');
   3525     *output = negative ? -v : v;
   3526     return src;
   3527 }
   3528 
   3529 // Parse display precision back from the display format string
   3530 // 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.
   3531 int ImParseFormatPrecision(const char* fmt, int default_precision)
   3532 {
   3533     fmt = ImParseFormatFindStart(fmt);
   3534     if (fmt[0] != '%')
   3535         return default_precision;
   3536     fmt++;
   3537     while (*fmt >= '0' && *fmt <= '9')
   3538         fmt++;
   3539     int precision = INT_MAX;
   3540     if (*fmt == '.')
   3541     {
   3542         fmt = ImAtoi<int>(fmt + 1, &precision);
   3543         if (precision < 0 || precision > 99)
   3544             precision = default_precision;
   3545     }
   3546     if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
   3547         precision = -1;
   3548     if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
   3549         precision = -1;
   3550     return (precision == INT_MAX) ? default_precision : precision;
   3551 }
   3552 
   3553 // Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
   3554 // FIXME: Facilitate using this in variety of other situations.
   3555 bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
   3556 {
   3557     // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
   3558     // We clear ActiveID on the first frame to allow the InputText() taking it back.
   3559     ImGuiContext& g = *GImGui;
   3560     const bool init = (g.TempInputId != id);
   3561     if (init)
   3562         ClearActiveID();
   3563 
   3564     g.CurrentWindow->DC.CursorPos = bb.Min;
   3565     bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
   3566     if (init)
   3567     {
   3568         // First frame we started displaying the InputText widget, we expect it to take the active id.
   3569         IM_ASSERT(g.ActiveId == id);
   3570         g.TempInputId = g.ActiveId;
   3571     }
   3572     return value_changed;
   3573 }
   3574 
   3575 // Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
   3576 // This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
   3577 // However this may not be ideal for all uses, as some user code may break on out of bound values.
   3578 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)
   3579 {
   3580     // FIXME: May need to clarify display behavior if format doesn't contain %.
   3581     // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405
   3582     const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
   3583     char fmt_buf[32];
   3584     char data_buf[32];
   3585     format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
   3586     if (format[0] == 0)
   3587         format = type_info->PrintFmt;
   3588     DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
   3589     ImStrTrimBlanks(data_buf);
   3590 
   3591     ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
   3592 
   3593     bool value_changed = false;
   3594     if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
   3595     {
   3596         // Backup old value
   3597         size_t data_type_size = type_info->Size;
   3598         ImGuiDataTypeStorage data_backup;
   3599         memcpy(&data_backup, p_data, data_type_size);
   3600 
   3601         // Apply new value (or operations) then clamp
   3602         DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL);
   3603         if (p_clamp_min || p_clamp_max)
   3604         {
   3605             if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
   3606                 ImSwap(p_clamp_min, p_clamp_max);
   3607             DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
   3608         }
   3609 
   3610         // Only mark as edited if new value is different
   3611         value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
   3612         if (value_changed)
   3613             MarkItemEdited(id);
   3614     }
   3615     return value_changed;
   3616 }
   3617 
   3618 void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data)
   3619 {
   3620     ImGuiContext& g = *GImGui;
   3621     g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasRefVal;
   3622     memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size);
   3623 }
   3624 
   3625 // 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.
   3626 // Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
   3627 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)
   3628 {
   3629     ImGuiWindow* window = GetCurrentWindow();
   3630     if (window->SkipItems)
   3631         return false;
   3632 
   3633     ImGuiContext& g = *GImGui;
   3634     ImGuiStyle& style = g.Style;
   3635 
   3636     if (format == NULL)
   3637         format = DataTypeGetInfo(data_type)->PrintFmt;
   3638 
   3639     void* p_data_default = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue;
   3640 
   3641     char buf[64];
   3642     if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0)
   3643         buf[0] = 0;
   3644     else
   3645         DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
   3646 
   3647     flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
   3648     flags |= (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
   3649 
   3650     bool value_changed = false;
   3651     if (p_step == NULL)
   3652     {
   3653         if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
   3654             value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
   3655     }
   3656     else
   3657     {
   3658         const float button_size = GetFrameHeight();
   3659 
   3660         BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
   3661         PushID(label);
   3662         SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
   3663         if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
   3664             value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
   3665         IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
   3666 
   3667         // Step buttons
   3668         const ImVec2 backup_frame_padding = style.FramePadding;
   3669         style.FramePadding.x = style.FramePadding.y;
   3670         ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
   3671         if (flags & ImGuiInputTextFlags_ReadOnly)
   3672             BeginDisabled();
   3673         SameLine(0, style.ItemInnerSpacing.x);
   3674         if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
   3675         {
   3676             DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
   3677             value_changed = true;
   3678         }
   3679         SameLine(0, style.ItemInnerSpacing.x);
   3680         if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
   3681         {
   3682             DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
   3683             value_changed = true;
   3684         }
   3685         if (flags & ImGuiInputTextFlags_ReadOnly)
   3686             EndDisabled();
   3687 
   3688         const char* label_end = FindRenderedTextEnd(label);
   3689         if (label != label_end)
   3690         {
   3691             SameLine(0, style.ItemInnerSpacing.x);
   3692             TextEx(label, label_end);
   3693         }
   3694         style.FramePadding = backup_frame_padding;
   3695 
   3696         PopID();
   3697         EndGroup();
   3698     }
   3699     if (value_changed)
   3700         MarkItemEdited(g.LastItemData.ID);
   3701 
   3702     return value_changed;
   3703 }
   3704 
   3705 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)
   3706 {
   3707     ImGuiWindow* window = GetCurrentWindow();
   3708     if (window->SkipItems)
   3709         return false;
   3710 
   3711     ImGuiContext& g = *GImGui;
   3712     bool value_changed = false;
   3713     BeginGroup();
   3714     PushID(label);
   3715     PushMultiItemsWidths(components, CalcItemWidth());
   3716     size_t type_size = GDataTypeInfo[data_type].Size;
   3717     for (int i = 0; i < components; i++)
   3718     {
   3719         PushID(i);
   3720         if (i > 0)
   3721             SameLine(0, g.Style.ItemInnerSpacing.x);
   3722         value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
   3723         PopID();
   3724         PopItemWidth();
   3725         p_data = (void*)((char*)p_data + type_size);
   3726     }
   3727     PopID();
   3728 
   3729     const char* label_end = FindRenderedTextEnd(label);
   3730     if (label != label_end)
   3731     {
   3732         SameLine(0.0f, g.Style.ItemInnerSpacing.x);
   3733         TextEx(label, label_end);
   3734     }
   3735 
   3736     EndGroup();
   3737     return value_changed;
   3738 }
   3739 
   3740 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
   3741 {
   3742     return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
   3743 }
   3744 
   3745 bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
   3746 {
   3747     return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
   3748 }
   3749 
   3750 bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
   3751 {
   3752     return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
   3753 }
   3754 
   3755 bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
   3756 {
   3757     return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
   3758 }
   3759 
   3760 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
   3761 {
   3762     // 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.
   3763     const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
   3764     return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
   3765 }
   3766 
   3767 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
   3768 {
   3769     return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
   3770 }
   3771 
   3772 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
   3773 {
   3774     return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
   3775 }
   3776 
   3777 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
   3778 {
   3779     return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
   3780 }
   3781 
   3782 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
   3783 {
   3784     return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
   3785 }
   3786 
   3787 //-------------------------------------------------------------------------
   3788 // [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
   3789 //-------------------------------------------------------------------------
   3790 // - InputText()
   3791 // - InputTextWithHint()
   3792 // - InputTextMultiline()
   3793 // - InputTextGetCharInfo() [Internal]
   3794 // - InputTextReindexLines() [Internal]
   3795 // - InputTextReindexLinesRange() [Internal]
   3796 // - InputTextEx() [Internal]
   3797 // - DebugNodeInputTextState() [Internal]
   3798 //-------------------------------------------------------------------------
   3799 
   3800 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
   3801 {
   3802     IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
   3803     return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
   3804 }
   3805 
   3806 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
   3807 {
   3808     return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
   3809 }
   3810 
   3811 bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
   3812 {
   3813     IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or  InputTextEx() manually if you need multi-line + hint.
   3814     return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
   3815 }
   3816 
   3817 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
   3818 {
   3819     int line_count = 0;
   3820     const char* s = text_begin;
   3821     while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
   3822         if (c == '\n')
   3823             line_count++;
   3824     s--;
   3825     if (s[0] != '\n' && s[0] != '\r')
   3826         line_count++;
   3827     *out_text_end = s;
   3828     return line_count;
   3829 }
   3830 
   3831 static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
   3832 {
   3833     ImGuiContext& g = *ctx;
   3834     ImFont* font = g.Font;
   3835     const float line_height = g.FontSize;
   3836     const float scale = line_height / font->FontSize;
   3837 
   3838     ImVec2 text_size = ImVec2(0, 0);
   3839     float line_width = 0.0f;
   3840 
   3841     const ImWchar* s = text_begin;
   3842     while (s < text_end)
   3843     {
   3844         unsigned int c = (unsigned int)(*s++);
   3845         if (c == '\n')
   3846         {
   3847             text_size.x = ImMax(text_size.x, line_width);
   3848             text_size.y += line_height;
   3849             line_width = 0.0f;
   3850             if (stop_on_new_line)
   3851                 break;
   3852             continue;
   3853         }
   3854         if (c == '\r')
   3855             continue;
   3856 
   3857         const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
   3858         line_width += char_width;
   3859     }
   3860 
   3861     if (text_size.x < line_width)
   3862         text_size.x = line_width;
   3863 
   3864     if (out_offset)
   3865         *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
   3866 
   3867     if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
   3868         text_size.y += line_height;
   3869 
   3870     if (remaining)
   3871         *remaining = s;
   3872 
   3873     return text_size;
   3874 }
   3875 
   3876 // 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)
   3877 namespace ImStb
   3878 {
   3879 
   3880 static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->CurLenW; }
   3881 static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { IM_ASSERT(idx <= obj->CurLenW); return obj->TextW[idx]; }
   3882 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 IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance(c) * g.FontScale; }
   3883 static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x200000 ? 0 : key; }
   3884 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
   3885 static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
   3886 {
   3887     const ImWchar* text = obj->TextW.Data;
   3888     const ImWchar* text_remaining = NULL;
   3889     const ImVec2 size = InputTextCalcTextSizeW(obj->Ctx, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
   3890     r->x0 = 0.0f;
   3891     r->x1 = size.x;
   3892     r->baseline_y_delta = size.y;
   3893     r->ymin = 0.0f;
   3894     r->ymax = size.y;
   3895     r->num_chars = (int)(text_remaining - (text + line_start_idx));
   3896 }
   3897 
   3898 static bool is_separator(unsigned int c)
   3899 {
   3900     return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|' || c=='\n' || c=='\r' || c=='.' || c=='!' || c=='\\' || c=='/';
   3901 }
   3902 
   3903 static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
   3904 {
   3905     // 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.
   3906     if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
   3907         return 0;
   3908 
   3909     bool prev_white = ImCharIsBlankW(obj->TextW[idx - 1]);
   3910     bool prev_separ = is_separator(obj->TextW[idx - 1]);
   3911     bool curr_white = ImCharIsBlankW(obj->TextW[idx]);
   3912     bool curr_separ = is_separator(obj->TextW[idx]);
   3913     return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
   3914 }
   3915 static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
   3916 {
   3917     if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
   3918         return 0;
   3919 
   3920     bool prev_white = ImCharIsBlankW(obj->TextW[idx]);
   3921     bool prev_separ = is_separator(obj->TextW[idx]);
   3922     bool curr_white = ImCharIsBlankW(obj->TextW[idx - 1]);
   3923     bool curr_separ = is_separator(obj->TextW[idx - 1]);
   3924     return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
   3925 }
   3926 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; }
   3927 static int  STB_TEXTEDIT_MOVEWORDRIGHT_MAC(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; }
   3928 static int  STB_TEXTEDIT_MOVEWORDRIGHT_WIN(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; }
   3929 static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)  { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }
   3930 #define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL  // They need to be #define for stb_textedit.h
   3931 #define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
   3932 
   3933 static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
   3934 {
   3935     ImWchar* dst = obj->TextW.Data + pos;
   3936 
   3937     // We maintain our buffer length in both UTF-8 and wchar formats
   3938     obj->Edited = true;
   3939     obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
   3940     obj->CurLenW -= n;
   3941 
   3942     // Offset remaining text (FIXME-OPT: Use memmove)
   3943     const ImWchar* src = obj->TextW.Data + pos + n;
   3944     while (ImWchar c = *src++)
   3945         *dst++ = c;
   3946     *dst = '\0';
   3947 }
   3948 
   3949 static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len)
   3950 {
   3951     const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
   3952     const int text_len = obj->CurLenW;
   3953     IM_ASSERT(pos <= text_len);
   3954 
   3955     const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
   3956     if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
   3957         return false;
   3958 
   3959     // Grow internal buffer if needed
   3960     if (new_text_len + text_len + 1 > obj->TextW.Size)
   3961     {
   3962         if (!is_resizable)
   3963             return false;
   3964         IM_ASSERT(text_len < obj->TextW.Size);
   3965         obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
   3966     }
   3967 
   3968     ImWchar* text = obj->TextW.Data;
   3969     if (pos != text_len)
   3970         memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
   3971     memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
   3972 
   3973     obj->Edited = true;
   3974     obj->CurLenW += new_text_len;
   3975     obj->CurLenA += new_text_len_utf8;
   3976     obj->TextW[obj->CurLenW] = '\0';
   3977 
   3978     return true;
   3979 }
   3980 
   3981 // 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)
   3982 #define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left
   3983 #define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right
   3984 #define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up
   3985 #define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down
   3986 #define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line
   3987 #define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line
   3988 #define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text
   3989 #define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text
   3990 #define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor
   3991 #define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor
   3992 #define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo
   3993 #define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo
   3994 #define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word
   3995 #define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word
   3996 #define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page
   3997 #define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page
   3998 #define STB_TEXTEDIT_K_SHIFT        0x400000
   3999 
   4000 #define IMSTB_TEXTEDIT_IMPLEMENTATION
   4001 #define IMSTB_TEXTEDIT_memmove memmove
   4002 #include "imstb_textedit.h"
   4003 
   4004 // stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
   4005 // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
   4006 static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
   4007 {
   4008     stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len);
   4009     ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW);
   4010     state->cursor = state->select_start = state->select_end = 0;
   4011     if (text_len <= 0)
   4012         return;
   4013     if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
   4014     {
   4015         state->cursor = state->select_start = state->select_end = text_len;
   4016         state->has_preferred_x = 0;
   4017         return;
   4018     }
   4019     IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
   4020 }
   4021 
   4022 } // namespace ImStb
   4023 
   4024 void ImGuiInputTextState::OnKeyPressed(int key)
   4025 {
   4026     stb_textedit_key(this, &Stb, key);
   4027     CursorFollow = true;
   4028     CursorAnimReset();
   4029 }
   4030 
   4031 ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
   4032 {
   4033     memset(this, 0, sizeof(*this));
   4034 }
   4035 
   4036 // Public API to manipulate UTF-8 text
   4037 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
   4038 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
   4039 void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
   4040 {
   4041     IM_ASSERT(pos + bytes_count <= BufTextLen);
   4042     char* dst = Buf + pos;
   4043     const char* src = Buf + pos + bytes_count;
   4044     while (char c = *src++)
   4045         *dst++ = c;
   4046     *dst = '\0';
   4047 
   4048     if (CursorPos >= pos + bytes_count)
   4049         CursorPos -= bytes_count;
   4050     else if (CursorPos >= pos)
   4051         CursorPos = pos;
   4052     SelectionStart = SelectionEnd = CursorPos;
   4053     BufDirty = true;
   4054     BufTextLen -= bytes_count;
   4055 }
   4056 
   4057 void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
   4058 {
   4059     // Accept null ranges
   4060     if (new_text == new_text_end)
   4061         return;
   4062 
   4063     const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
   4064     const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
   4065     if (new_text_len + BufTextLen >= BufSize)
   4066     {
   4067         if (!is_resizable)
   4068             return;
   4069 
   4070         // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
   4071         ImGuiContext& g = *Ctx;
   4072         ImGuiInputTextState* edit_state = &g.InputTextState;
   4073         IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
   4074         IM_ASSERT(Buf == edit_state->TextA.Data);
   4075         int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
   4076         edit_state->TextA.reserve(new_buf_size + 1);
   4077         Buf = edit_state->TextA.Data;
   4078         BufSize = edit_state->BufCapacityA = new_buf_size;
   4079     }
   4080 
   4081     if (BufTextLen != pos)
   4082         memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
   4083     memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
   4084     Buf[BufTextLen + new_text_len] = '\0';
   4085 
   4086     if (CursorPos >= pos)
   4087         CursorPos += new_text_len;
   4088     SelectionStart = SelectionEnd = CursorPos;
   4089     BufDirty = true;
   4090     BufTextLen += new_text_len;
   4091 }
   4092 
   4093 // Return false to discard a character.
   4094 static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
   4095 {
   4096     unsigned int c = *p_char;
   4097 
   4098     // Filter non-printable (NB: isprint is unreliable! see #2467)
   4099     bool apply_named_filters = true;
   4100     if (c < 0x20)
   4101     {
   4102         bool pass = false;
   4103         pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
   4104         pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
   4105         if (!pass)
   4106             return false;
   4107         apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
   4108     }
   4109 
   4110     if (input_source_is_clipboard == false)
   4111     {
   4112         // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
   4113         if (c == 127)
   4114             return false;
   4115 
   4116         // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
   4117         if (c >= 0xE000 && c <= 0xF8FF)
   4118             return false;
   4119     }
   4120 
   4121     // Filter Unicode ranges we are not handling in this build
   4122     if (c > IM_UNICODE_CODEPOINT_MAX)
   4123         return false;
   4124 
   4125     // Generic named filters
   4126     if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))
   4127     {
   4128         // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'.
   4129         // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
   4130         // 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.
   4131         // Change the default decimal_point with:
   4132         //   ImGui::GetIO()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
   4133         // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions.
   4134         ImGuiContext& g = *ctx;
   4135         const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint;
   4136         if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
   4137             if (c == '.' || c == ',')
   4138                 c = c_decimal_point;
   4139 
   4140         // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
   4141         // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
   4142         // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
   4143         if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
   4144             if (c >= 0xFF01 && c <= 0xFF5E)
   4145                 c = c - 0xFF01 + 0x21;
   4146 
   4147         // Allow 0-9 . - + * /
   4148         if (flags & ImGuiInputTextFlags_CharsDecimal)
   4149             if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
   4150                 return false;
   4151 
   4152         // Allow 0-9 . - + * / e E
   4153         if (flags & ImGuiInputTextFlags_CharsScientific)
   4154             if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
   4155                 return false;
   4156 
   4157         // Allow 0-9 a-F A-F
   4158         if (flags & ImGuiInputTextFlags_CharsHexadecimal)
   4159             if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
   4160                 return false;
   4161 
   4162         // Turn a-z into A-Z
   4163         if (flags & ImGuiInputTextFlags_CharsUppercase)
   4164             if (c >= 'a' && c <= 'z')
   4165                 c += (unsigned int)('A' - 'a');
   4166 
   4167         if (flags & ImGuiInputTextFlags_CharsNoBlank)
   4168             if (ImCharIsBlankW(c))
   4169                 return false;
   4170 
   4171         *p_char = c;
   4172     }
   4173 
   4174     // Custom callback filter
   4175     if (flags & ImGuiInputTextFlags_CallbackCharFilter)
   4176     {
   4177         ImGuiContext& g = *GImGui;
   4178         ImGuiInputTextCallbackData callback_data;
   4179         callback_data.Ctx = &g;
   4180         callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
   4181         callback_data.EventChar = (ImWchar)c;
   4182         callback_data.Flags = flags;
   4183         callback_data.UserData = user_data;
   4184         if (callback(&callback_data) != 0)
   4185             return false;
   4186         *p_char = callback_data.EventChar;
   4187         if (!callback_data.EventChar)
   4188             return false;
   4189     }
   4190 
   4191     return true;
   4192 }
   4193 
   4194 // Find the shortest single replacement we can make to get the new text from the old text.
   4195 // Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end.
   4196 // FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly.
   4197 static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a)
   4198 {
   4199     ImGuiContext& g = *GImGui;
   4200     const ImWchar* old_buf = state->TextW.Data;
   4201     const int old_length = state->CurLenW;
   4202     const int new_length = ImTextCountCharsFromUtf8(new_buf_a, new_buf_a + new_length_a);
   4203     g.TempBuffer.reserve_discard((new_length + 1) * sizeof(ImWchar));
   4204     ImWchar* new_buf = (ImWchar*)(void*)g.TempBuffer.Data;
   4205     ImTextStrFromUtf8(new_buf, new_length + 1, new_buf_a, new_buf_a + new_length_a);
   4206 
   4207     const int shorter_length = ImMin(old_length, new_length);
   4208     int first_diff;
   4209     for (first_diff = 0; first_diff < shorter_length; first_diff++)
   4210         if (old_buf[first_diff] != new_buf[first_diff])
   4211             break;
   4212     if (first_diff == old_length && first_diff == new_length)
   4213         return;
   4214 
   4215     int old_last_diff = old_length - 1;
   4216     int new_last_diff = new_length - 1;
   4217     for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
   4218         if (old_buf[old_last_diff] != new_buf[new_last_diff])
   4219             break;
   4220 
   4221     const int insert_len = new_last_diff - first_diff + 1;
   4222     const int delete_len = old_last_diff - first_diff + 1;
   4223     if (insert_len > 0 || delete_len > 0)
   4224         if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len))
   4225             for (int i = 0; i < delete_len; i++)
   4226                 p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i);
   4227 }
   4228 
   4229 // As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables)
   4230 // we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714)
   4231 // It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick,
   4232 // but that more likely be attractive when we do have _NoLiveEdit flag available.
   4233 void ImGui::InputTextDeactivateHook(ImGuiID id)
   4234 {
   4235     ImGuiContext& g = *GImGui;
   4236     ImGuiInputTextState* state = &g.InputTextState;
   4237     if (id == 0 || state->ID != id)
   4238         return;
   4239     g.InputTextDeactivatedState.ID = state->ID;
   4240     if (state->Flags & ImGuiInputTextFlags_ReadOnly)
   4241     {
   4242         g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat.
   4243     }
   4244     else
   4245     {
   4246         IM_ASSERT(state->TextA.Data != 0);
   4247         g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1);
   4248         memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 1);
   4249     }
   4250 }
   4251 
   4252 // Edit a string of text
   4253 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
   4254 //   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
   4255 //   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
   4256 // - 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.
   4257 // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
   4258 // (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
   4259 //  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)
   4260 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)
   4261 {
   4262     ImGuiWindow* window = GetCurrentWindow();
   4263     if (window->SkipItems)
   4264         return false;
   4265 
   4266     IM_ASSERT(buf != NULL && buf_size >= 0);
   4267     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
   4268     IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
   4269 
   4270     ImGuiContext& g = *GImGui;
   4271     ImGuiIO& io = g.IO;
   4272     const ImGuiStyle& style = g.Style;
   4273 
   4274     const bool RENDER_SELECTION_WHEN_INACTIVE = false;
   4275     const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
   4276 
   4277     if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)
   4278         BeginGroup();
   4279     const ImGuiID id = window->GetID(label);
   4280     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   4281     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
   4282     const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
   4283 
   4284     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
   4285     const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
   4286 
   4287     ImGuiWindow* draw_window = window;
   4288     ImVec2 inner_size = frame_size;
   4289     ImGuiLastItemData item_data_backup;
   4290     if (is_multiline)
   4291     {
   4292         ImVec2 backup_pos = window->DC.CursorPos;
   4293         ItemSize(total_bb, style.FramePadding.y);
   4294         if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
   4295         {
   4296             EndGroup();
   4297             return false;
   4298         }
   4299         item_data_backup = g.LastItemData;
   4300         window->DC.CursorPos = backup_pos;
   4301 
   4302         // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
   4303         if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))
   4304             g.NavActivateId = 0;
   4305 
   4306         // Prevent NavActivate reactivating in BeginChild() when we are already active.
   4307         const ImGuiID backup_activate_id = g.NavActivateId;
   4308         if (g.ActiveId == id) // Prevent reactivation
   4309             g.NavActivateId = 0;
   4310 
   4311         // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
   4312         PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
   4313         PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
   4314         PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
   4315         PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
   4316         bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Border, ImGuiWindowFlags_NoMove);
   4317         g.NavActivateId = backup_activate_id;
   4318         PopStyleVar(3);
   4319         PopStyleColor();
   4320         if (!child_visible)
   4321         {
   4322             EndChild();
   4323             EndGroup();
   4324             return false;
   4325         }
   4326         draw_window = g.CurrentWindow; // Child window
   4327         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.
   4328         draw_window->DC.CursorPos += style.FramePadding;
   4329         inner_size.x -= draw_window->ScrollbarSizes.x;
   4330     }
   4331     else
   4332     {
   4333         // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
   4334         ItemSize(total_bb, style.FramePadding.y);
   4335         if (!(flags & ImGuiInputTextFlags_MergedItem))
   4336             if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
   4337                 return false;
   4338     }
   4339     const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
   4340     if (hovered)
   4341         g.MouseCursor = ImGuiMouseCursor_TextInput;
   4342 
   4343     // We are only allowed to access the state if we are already the active widget.
   4344     ImGuiInputTextState* state = GetInputTextState(id);
   4345 
   4346     if (g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly)
   4347         flags |= ImGuiInputTextFlags_ReadOnly;
   4348     const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
   4349     const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
   4350     const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
   4351     const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
   4352     if (is_resizable)
   4353         IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
   4354 
   4355     const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard || g.NavInputSource == ImGuiInputSource_Gamepad)));
   4356 
   4357     const bool user_clicked = hovered && io.MouseClicked[0];
   4358     const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
   4359     const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
   4360     bool clear_active_id = false;
   4361     bool select_all = false;
   4362 
   4363     float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
   4364 
   4365     const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf);
   4366     const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); // state != NULL means its our state.
   4367     const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
   4368     const bool init_state = (init_make_active || user_scroll_active);
   4369     if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf)
   4370     {
   4371         // Access state even if we don't own it yet.
   4372         state = &g.InputTextState;
   4373         state->CursorAnimReset();
   4374         state->ReloadUserBuf = false;
   4375 
   4376         // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)
   4377         InputTextDeactivateHook(state->ID);
   4378 
   4379         // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
   4380         const int buf_len = (int)strlen(buf);
   4381         if (!init_reload_from_user_buf)
   4382         {
   4383             // Take a copy of the initial buffer value.
   4384             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.
   4385             memcpy(state->InitialTextA.Data, buf, buf_len + 1);
   4386         }
   4387 
   4388         // Preserve cursor position and undo/redo stack if we come back to same widget
   4389         // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
   4390         bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf);
   4391         if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0)))
   4392             recycle_state = false;
   4393 
   4394         // Start edition
   4395         const char* buf_end = NULL;
   4396         state->ID = id;
   4397         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.
   4398         state->TextA.resize(0);
   4399         state->TextAIsValid = false;                // TextA is not valid yet (we will display buf until then)
   4400         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
   4401         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.
   4402 
   4403         if (recycle_state)
   4404         {
   4405             // Recycle existing cursor/selection/undo stack but clamp position
   4406             // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
   4407             state->CursorClamp();
   4408         }
   4409         else
   4410         {
   4411             state->ScrollX = 0.0f;
   4412             stb_textedit_initialize_state(&state->Stb, !is_multiline);
   4413         }
   4414 
   4415         if (init_reload_from_user_buf)
   4416         {
   4417             state->Stb.select_start = state->ReloadSelectionStart;
   4418             state->Stb.cursor = state->Stb.select_end = state->ReloadSelectionEnd;
   4419             state->CursorClamp();
   4420         }
   4421         else if (!is_multiline)
   4422         {
   4423             if (flags & ImGuiInputTextFlags_AutoSelectAll)
   4424                 select_all = true;
   4425             if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
   4426                 select_all = true;
   4427             if (user_clicked && io.KeyCtrl)
   4428                 select_all = true;
   4429         }
   4430 
   4431         if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
   4432             state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863)
   4433     }
   4434 
   4435     const bool is_osx = io.ConfigMacOSXBehaviors;
   4436     if (g.ActiveId != id && init_make_active)
   4437     {
   4438         IM_ASSERT(state && state->ID == id);
   4439         SetActiveID(id, window);
   4440         SetFocusID(id, window);
   4441         FocusWindow(window);
   4442     }
   4443     if (g.ActiveId == id)
   4444     {
   4445         // Declare some inputs, the other are registered and polled via Shortcut() routing system.
   4446         if (user_clicked)
   4447             SetKeyOwner(ImGuiKey_MouseLeft, id);
   4448         g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
   4449         if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
   4450             g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
   4451         SetKeyOwner(ImGuiKey_Enter, id);
   4452         SetKeyOwner(ImGuiKey_KeypadEnter, id);
   4453         SetKeyOwner(ImGuiKey_Home, id);
   4454         SetKeyOwner(ImGuiKey_End, id);
   4455         if (is_multiline)
   4456         {
   4457             SetKeyOwner(ImGuiKey_PageUp, id);
   4458             SetKeyOwner(ImGuiKey_PageDown, id);
   4459         }
   4460         // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu
   4461         if (is_osx)
   4462             SetKeyOwner(ImGuiMod_Alt, id);
   4463     }
   4464 
   4465     // 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)
   4466     if (g.ActiveId == id && state == NULL)
   4467         ClearActiveID();
   4468 
   4469     // Release focus when we click outside
   4470     if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
   4471         clear_active_id = true;
   4472 
   4473     // Lock the decision of whether we are going to take the path displaying the cursor or selection
   4474     bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
   4475     bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
   4476     bool value_changed = false;
   4477     bool validated = false;
   4478 
   4479     // When read-only we always use the live data passed to the function
   4480     // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
   4481     if (is_readonly && state != NULL && (render_cursor || render_selection))
   4482     {
   4483         const char* buf_end = NULL;
   4484         state->TextW.resize(buf_size + 1);
   4485         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
   4486         state->CurLenA = (int)(buf_end - buf);
   4487         state->CursorClamp();
   4488         render_selection &= state->HasSelection();
   4489     }
   4490 
   4491     // Select the buffer to render.
   4492     const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid;
   4493     const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
   4494 
   4495     // Password pushes a temporary font with only a fallback glyph
   4496     if (is_password && !is_displaying_hint)
   4497     {
   4498         const ImFontGlyph* glyph = g.Font->FindGlyph('*');
   4499         ImFont* password_font = &g.InputTextPasswordFont;
   4500         password_font->FontSize = g.Font->FontSize;
   4501         password_font->Scale = g.Font->Scale;
   4502         password_font->Ascent = g.Font->Ascent;
   4503         password_font->Descent = g.Font->Descent;
   4504         password_font->ContainerAtlas = g.Font->ContainerAtlas;
   4505         password_font->FallbackGlyph = glyph;
   4506         password_font->FallbackAdvanceX = glyph->AdvanceX;
   4507         IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
   4508         PushFont(password_font);
   4509     }
   4510 
   4511     // Process mouse inputs and character inputs
   4512     int backup_current_text_length = 0;
   4513     if (g.ActiveId == id)
   4514     {
   4515         IM_ASSERT(state != NULL);
   4516         backup_current_text_length = state->CurLenA;
   4517         state->Edited = false;
   4518         state->BufCapacityA = buf_size;
   4519         state->Flags = flags;
   4520 
   4521         // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
   4522         // Down the line we should have a cleaner library-wide concept of Selected vs Active.
   4523         g.ActiveIdAllowOverlap = !io.MouseDown[0];
   4524 
   4525         // Edit in progress
   4526         const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX;
   4527         const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
   4528 
   4529         if (select_all)
   4530         {
   4531             state->SelectAll();
   4532             state->SelectedAllMouseLock = true;
   4533         }
   4534         else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
   4535         {
   4536             stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
   4537             const int multiclick_count = (io.MouseClickedCount[0] - 2);
   4538             if ((multiclick_count % 2) == 0)
   4539             {
   4540                 // Double-click: Select word
   4541                 // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant:
   4542                 // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS)
   4543                 const bool is_bol = (state->Stb.cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor - 1) == '\n';
   4544                 if (STB_TEXT_HAS_SELECTION(&state->Stb) || !is_bol)
   4545                     state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
   4546                 //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
   4547                 if (!STB_TEXT_HAS_SELECTION(&state->Stb))
   4548                     ImStb::stb_textedit_prep_selection_at_cursor(&state->Stb);
   4549                 state->Stb.cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb.cursor);
   4550                 state->Stb.select_end = state->Stb.cursor;
   4551                 ImStb::stb_textedit_clamp(state, &state->Stb);
   4552             }
   4553             else
   4554             {
   4555                 // Triple-click: Select line
   4556                 const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor) == '\n';
   4557                 state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
   4558                 state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
   4559                 state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
   4560                 if (!is_eol && is_multiline)
   4561                 {
   4562                     ImSwap(state->Stb.select_start, state->Stb.select_end);
   4563                     state->Stb.cursor = state->Stb.select_end;
   4564                 }
   4565                 state->CursorFollow = false;
   4566             }
   4567             state->CursorAnimReset();
   4568         }
   4569         else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
   4570         {
   4571             if (hovered)
   4572             {
   4573                 if (io.KeyShift)
   4574                     stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
   4575                 else
   4576                     stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
   4577                 state->CursorAnimReset();
   4578             }
   4579         }
   4580         else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
   4581         {
   4582             stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
   4583             state->CursorAnimReset();
   4584             state->CursorFollow = true;
   4585         }
   4586         if (state->SelectedAllMouseLock && !io.MouseDown[0])
   4587             state->SelectedAllMouseLock = false;
   4588 
   4589         // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
   4590         // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes)
   4591         if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
   4592         {
   4593             if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))
   4594             {
   4595                 unsigned int c = '\t'; // Insert TAB
   4596                 if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
   4597                     state->OnKeyPressed((int)c);
   4598             }
   4599             // FIXME: Implement Shift+Tab
   4600             /*
   4601             if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id))
   4602             {
   4603             }
   4604             */
   4605         }
   4606 
   4607         // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
   4608         // 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.
   4609         const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl);
   4610         if (io.InputQueueCharacters.Size > 0)
   4611         {
   4612             if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)
   4613                 for (int n = 0; n < io.InputQueueCharacters.Size; n++)
   4614                 {
   4615                     // Insert character if they pass filtering
   4616                     unsigned int c = (unsigned int)io.InputQueueCharacters[n];
   4617                     if (c == '\t') // Skip Tab, see above.
   4618                         continue;
   4619                     if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
   4620                         state->OnKeyPressed((int)c);
   4621                 }
   4622 
   4623             // Consume characters
   4624             io.InputQueueCharacters.resize(0);
   4625         }
   4626     }
   4627 
   4628     // Process other shortcuts/key-presses
   4629     bool revert_edit = false;
   4630     if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
   4631     {
   4632         IM_ASSERT(state != NULL);
   4633 
   4634         const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
   4635         state->Stb.row_count_per_page = row_count_per_page;
   4636 
   4637         const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
   4638         const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
   4639         const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
   4640 
   4641         // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText)
   4642         // Otherwise we could simply assume that we own the keys as we are active.
   4643         const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
   4644         const bool is_cut   = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
   4645         const bool is_copy  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0,        id) || Shortcut(ImGuiMod_Ctrl  | ImGuiKey_Insert, 0,        id)) && !is_password && (!is_multiline || state->HasSelection());
   4646         const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly;
   4647         const bool is_undo  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
   4648         const bool is_redo =  (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable;
   4649         const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);
   4650 
   4651         // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.
   4652         const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
   4653         const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
   4654         const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));
   4655         const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id));
   4656 
   4657         // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of.
   4658         // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line.
   4659         if (IsKeyPressed(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); }
   4660         else if (IsKeyPressed(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); }
   4661         else if (IsKeyPressed(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); }
   4662         else if (IsKeyPressed(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); }
   4663         else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline)      { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
   4664         else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline)    { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
   4665         else if (IsKeyPressed(ImGuiKey_Home))                        { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
   4666         else if (IsKeyPressed(ImGuiKey_End))                         { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
   4667         else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)
   4668         {
   4669             if (!state->HasSelection())
   4670             {
   4671                 // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace)
   4672                 if (is_wordmove_key_down)
   4673                     state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
   4674             }
   4675             state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
   4676         }
   4677         else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)
   4678         {
   4679             if (!state->HasSelection())
   4680             {
   4681                 if (is_wordmove_key_down)
   4682                     state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
   4683                 else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper)
   4684                     state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
   4685             }
   4686             state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
   4687         }
   4688         else if (is_enter_pressed || is_gamepad_validate)
   4689         {
   4690             // Determine if we turn Enter into a \n character
   4691             bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
   4692             if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
   4693             {
   4694                 validated = true;
   4695                 if (io.ConfigInputTextEnterKeepActive && !is_multiline)
   4696                     state->SelectAll(); // No need to scroll
   4697                 else
   4698                     clear_active_id = true;
   4699             }
   4700             else if (!is_readonly)
   4701             {
   4702                 unsigned int c = '\n'; // Insert new line
   4703                 if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
   4704                     state->OnKeyPressed((int)c);
   4705             }
   4706         }
   4707         else if (is_cancel)
   4708         {
   4709             if (flags & ImGuiInputTextFlags_EscapeClearsAll)
   4710             {
   4711                 if (buf[0] != 0)
   4712                 {
   4713                     revert_edit = true;
   4714                 }
   4715                 else
   4716                 {
   4717                     render_cursor = render_selection = false;
   4718                     clear_active_id = true;
   4719                 }
   4720             }
   4721             else
   4722             {
   4723                 clear_active_id = revert_edit = true;
   4724                 render_cursor = render_selection = false;
   4725             }
   4726         }
   4727         else if (is_undo || is_redo)
   4728         {
   4729             state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
   4730             state->ClearSelection();
   4731         }
   4732         else if (is_select_all)
   4733         {
   4734             state->SelectAll();
   4735             state->CursorFollow = true;
   4736         }
   4737         else if (is_cut || is_copy)
   4738         {
   4739             // Cut, Copy
   4740             if (io.SetClipboardTextFn)
   4741             {
   4742                 const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0;
   4743                 const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
   4744                 const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1;
   4745                 char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char));
   4746                 ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie);
   4747                 SetClipboardText(clipboard_data);
   4748                 MemFree(clipboard_data);
   4749             }
   4750             if (is_cut)
   4751             {
   4752                 if (!state->HasSelection())
   4753                     state->SelectAll();
   4754                 state->CursorFollow = true;
   4755                 stb_textedit_cut(state, &state->Stb);
   4756             }
   4757         }
   4758         else if (is_paste)
   4759         {
   4760             if (const char* clipboard = GetClipboardText())
   4761             {
   4762                 // Filter pasted buffer
   4763                 const int clipboard_len = (int)strlen(clipboard);
   4764                 ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar));
   4765                 int clipboard_filtered_len = 0;
   4766                 for (const char* s = clipboard; *s != 0; )
   4767                 {
   4768                     unsigned int c;
   4769                     s += ImTextCharFromUtf8(&c, s, NULL);
   4770                     if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))
   4771                         continue;
   4772                     clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
   4773                 }
   4774                 clipboard_filtered[clipboard_filtered_len] = 0;
   4775                 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
   4776                 {
   4777                     stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len);
   4778                     state->CursorFollow = true;
   4779                 }
   4780                 MemFree(clipboard_filtered);
   4781             }
   4782         }
   4783 
   4784         // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
   4785         render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
   4786     }
   4787 
   4788     // Process callbacks and apply result back to user's buffer.
   4789     const char* apply_new_text = NULL;
   4790     int apply_new_text_length = 0;
   4791     if (g.ActiveId == id)
   4792     {
   4793         IM_ASSERT(state != NULL);
   4794         if (revert_edit && !is_readonly)
   4795         {
   4796             if (flags & ImGuiInputTextFlags_EscapeClearsAll)
   4797             {
   4798                 // Clear input
   4799                 IM_ASSERT(buf[0] != 0);
   4800                 apply_new_text = "";
   4801                 apply_new_text_length = 0;
   4802                 value_changed = true;
   4803                 IMSTB_TEXTEDIT_CHARTYPE empty_string;
   4804                 stb_textedit_replace(state, &state->Stb, &empty_string, 0);
   4805             }
   4806             else if (strcmp(buf, state->InitialTextA.Data) != 0)
   4807             {
   4808                 // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
   4809                 // Push records into the undo stack so we can CTRL+Z the revert operation itself
   4810                 apply_new_text = state->InitialTextA.Data;
   4811                 apply_new_text_length = state->InitialTextA.Size - 1;
   4812                 value_changed = true;
   4813                 ImVector<ImWchar> w_text;
   4814                 if (apply_new_text_length > 0)
   4815                 {
   4816                     w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1);
   4817                     ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length);
   4818                 }
   4819                 stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0);
   4820             }
   4821         }
   4822 
   4823         // Apply ASCII value
   4824         if (!is_readonly)
   4825         {
   4826             state->TextAIsValid = true;
   4827             state->TextA.resize(state->TextW.Size * 4 + 1);
   4828             ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
   4829         }
   4830 
   4831         // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer
   4832         // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
   4833         // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
   4834         // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage
   4835         // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object
   4836         // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
   4837         const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
   4838         if (apply_edit_back_to_user_buffer)
   4839         {
   4840             // Apply new value immediately - copy modified buffer back
   4841             // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
   4842             // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
   4843             // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
   4844 
   4845             // User callback
   4846             if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
   4847             {
   4848                 IM_ASSERT(callback != NULL);
   4849 
   4850                 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
   4851                 ImGuiInputTextFlags event_flag = 0;
   4852                 ImGuiKey event_key = ImGuiKey_None;
   4853                 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
   4854                 {
   4855                     event_flag = ImGuiInputTextFlags_CallbackCompletion;
   4856                     event_key = ImGuiKey_Tab;
   4857                 }
   4858                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
   4859                 {
   4860                     event_flag = ImGuiInputTextFlags_CallbackHistory;
   4861                     event_key = ImGuiKey_UpArrow;
   4862                 }
   4863                 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
   4864                 {
   4865                     event_flag = ImGuiInputTextFlags_CallbackHistory;
   4866                     event_key = ImGuiKey_DownArrow;
   4867                 }
   4868                 else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
   4869                 {
   4870                     event_flag = ImGuiInputTextFlags_CallbackEdit;
   4871                 }
   4872                 else if (flags & ImGuiInputTextFlags_CallbackAlways)
   4873                 {
   4874                     event_flag = ImGuiInputTextFlags_CallbackAlways;
   4875                 }
   4876 
   4877                 if (event_flag)
   4878                 {
   4879                     ImGuiInputTextCallbackData callback_data;
   4880                     callback_data.Ctx = &g;
   4881                     callback_data.EventFlag = event_flag;
   4882                     callback_data.Flags = flags;
   4883                     callback_data.UserData = callback_user_data;
   4884 
   4885                     char* callback_buf = is_readonly ? buf : state->TextA.Data;
   4886                     callback_data.EventKey = event_key;
   4887                     callback_data.Buf = callback_buf;
   4888                     callback_data.BufTextLen = state->CurLenA;
   4889                     callback_data.BufSize = state->BufCapacityA;
   4890                     callback_data.BufDirty = false;
   4891 
   4892                     // 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)
   4893                     ImWchar* text = state->TextW.Data;
   4894                     const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor);
   4895                     const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start);
   4896                     const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end);
   4897 
   4898                     // Call user code
   4899                     callback(&callback_data);
   4900 
   4901                     // Read back what user may have modified
   4902                     callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback
   4903                     IM_ASSERT(callback_data.Buf == callback_buf);         // Invalid to modify those fields
   4904                     IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
   4905                     IM_ASSERT(callback_data.Flags == flags);
   4906                     const bool buf_dirty = callback_data.BufDirty;
   4907                     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; }
   4908                     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); }
   4909                     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); }
   4910                     if (buf_dirty)
   4911                     {
   4912                         IM_ASSERT(!is_readonly);
   4913                         IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
   4914                         InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ?
   4915                         if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
   4916                             state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); // Worse case scenario resize
   4917                         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
   4918                         state->CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
   4919                         state->CursorAnimReset();
   4920                     }
   4921                 }
   4922             }
   4923 
   4924             // Will copy result string if modified
   4925             if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
   4926             {
   4927                 apply_new_text = state->TextA.Data;
   4928                 apply_new_text_length = state->CurLenA;
   4929                 value_changed = true;
   4930             }
   4931         }
   4932     }
   4933 
   4934     // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
   4935     if (g.InputTextDeactivatedState.ID == id)
   4936     {
   4937         if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
   4938         {
   4939             apply_new_text = g.InputTextDeactivatedState.TextA.Data;
   4940             apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
   4941             value_changed = true;
   4942             //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
   4943         }
   4944         g.InputTextDeactivatedState.ID = 0;
   4945     }
   4946 
   4947     // Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
   4948     if (apply_new_text != NULL)
   4949     {
   4950         // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
   4951         // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
   4952         // without any storage on user's side.
   4953         IM_ASSERT(apply_new_text_length >= 0);
   4954         if (is_resizable)
   4955         {
   4956             ImGuiInputTextCallbackData callback_data;
   4957             callback_data.Ctx = &g;
   4958             callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
   4959             callback_data.Flags = flags;
   4960             callback_data.Buf = buf;
   4961             callback_data.BufTextLen = apply_new_text_length;
   4962             callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
   4963             callback_data.UserData = callback_user_data;
   4964             callback(&callback_data);
   4965             buf = callback_data.Buf;
   4966             buf_size = callback_data.BufSize;
   4967             apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
   4968             IM_ASSERT(apply_new_text_length <= buf_size);
   4969         }
   4970         //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
   4971 
   4972         // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
   4973         ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
   4974     }
   4975 
   4976     // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
   4977     // Otherwise request text input ahead for next frame.
   4978     if (g.ActiveId == id && clear_active_id)
   4979         ClearActiveID();
   4980     else if (g.ActiveId == id)
   4981         g.WantTextInputNextFrame = 1;
   4982 
   4983     // Render frame
   4984     if (!is_multiline)
   4985     {
   4986         RenderNavHighlight(frame_bb, id);
   4987         RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
   4988     }
   4989 
   4990     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
   4991     ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
   4992     ImVec2 text_size(0.0f, 0.0f);
   4993 
   4994     // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
   4995     // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
   4996     // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
   4997     const int buf_display_max_length = 2 * 1024 * 1024;
   4998     const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
   4999     const char* buf_display_end = NULL; // We have specialized paths below for setting the length
   5000     if (is_displaying_hint)
   5001     {
   5002         buf_display = hint;
   5003         buf_display_end = hint + strlen(hint);
   5004     }
   5005 
   5006     // Render text. We currently only render selection when the widget is active or while scrolling.
   5007     // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
   5008     if (render_cursor || render_selection)
   5009     {
   5010         IM_ASSERT(state != NULL);
   5011         if (!is_displaying_hint)
   5012             buf_display_end = buf_display + state->CurLenA;
   5013 
   5014         // Render text (with cursor and selection)
   5015         // This is going to be messy. We need to:
   5016         // - Display the text (this alone can be more easily clipped)
   5017         // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
   5018         // - Measure text height (for scrollbar)
   5019         // 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)
   5020         // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
   5021         const ImWchar* text_begin = state->TextW.Data;
   5022         ImVec2 cursor_offset, select_start_offset;
   5023 
   5024         {
   5025             // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions.
   5026             const ImWchar* searches_input_ptr[2] = { NULL, NULL };
   5027             int searches_result_line_no[2] = { -1000, -1000 };
   5028             int searches_remaining = 0;
   5029             if (render_cursor)
   5030             {
   5031                 searches_input_ptr[0] = text_begin + state->Stb.cursor;
   5032                 searches_result_line_no[0] = -1;
   5033                 searches_remaining++;
   5034             }
   5035             if (render_selection)
   5036             {
   5037                 searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
   5038                 searches_result_line_no[1] = -1;
   5039                 searches_remaining++;
   5040             }
   5041 
   5042             // Iterate all lines to find our line numbers
   5043             // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
   5044             searches_remaining += is_multiline ? 1 : 0;
   5045             int line_count = 0;
   5046             //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
   5047             for (const ImWchar* s = text_begin; *s != 0; s++)
   5048                 if (*s == '\n')
   5049                 {
   5050                     line_count++;
   5051                     if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; }
   5052                     if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; }
   5053                 }
   5054             line_count++;
   5055             if (searches_result_line_no[0] == -1)
   5056                 searches_result_line_no[0] = line_count;
   5057             if (searches_result_line_no[1] == -1)
   5058                 searches_result_line_no[1] = line_count;
   5059 
   5060             // Calculate 2d position by finding the beginning of the line and measuring distance
   5061             cursor_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
   5062             cursor_offset.y = searches_result_line_no[0] * g.FontSize;
   5063             if (searches_result_line_no[1] >= 0)
   5064             {
   5065                 select_start_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
   5066                 select_start_offset.y = searches_result_line_no[1] * g.FontSize;
   5067             }
   5068 
   5069             // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
   5070             if (is_multiline)
   5071                 text_size = ImVec2(inner_size.x, line_count * g.FontSize);
   5072         }
   5073 
   5074         // Scroll
   5075         if (render_cursor && state->CursorFollow)
   5076         {
   5077             // Horizontal scroll in chunks of quarter width
   5078             if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
   5079             {
   5080                 const float scroll_increment_x = inner_size.x * 0.25f;
   5081                 const float visible_width = inner_size.x - style.FramePadding.x;
   5082                 if (cursor_offset.x < state->ScrollX)
   5083                     state->ScrollX = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
   5084                 else if (cursor_offset.x - visible_width >= state->ScrollX)
   5085                     state->ScrollX = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);
   5086             }
   5087             else
   5088             {
   5089                 state->ScrollX = 0.0f;
   5090             }
   5091 
   5092             // Vertical scroll
   5093             if (is_multiline)
   5094             {
   5095                 // Test if cursor is vertically visible
   5096                 if (cursor_offset.y - g.FontSize < scroll_y)
   5097                     scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
   5098                 else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
   5099                     scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
   5100                 const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
   5101                 scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
   5102                 draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
   5103                 draw_window->Scroll.y = scroll_y;
   5104             }
   5105 
   5106             state->CursorFollow = false;
   5107         }
   5108 
   5109         // Draw selection
   5110         const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
   5111         if (render_selection)
   5112         {
   5113             const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
   5114             const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
   5115 
   5116             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.
   5117             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.
   5118             float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
   5119             ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
   5120             for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
   5121             {
   5122                 if (rect_pos.y > clip_rect.w + g.FontSize)
   5123                     break;
   5124                 if (rect_pos.y < clip_rect.y)
   5125                 {
   5126                     //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p);  // FIXME-OPT: Could use this when wchar_t are 16-bit
   5127                     //p = p ? p + 1 : text_selected_end;
   5128                     while (p < text_selected_end)
   5129                         if (*p++ == '\n')
   5130                             break;
   5131                 }
   5132                 else
   5133                 {
   5134                     ImVec2 rect_size = InputTextCalcTextSizeW(&g, p, text_selected_end, &p, NULL, true);
   5135                     if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
   5136                     ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
   5137                     rect.ClipWith(clip_rect);
   5138                     if (rect.Overlaps(clip_rect))
   5139                         draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
   5140                 }
   5141                 rect_pos.x = draw_pos.x - draw_scroll.x;
   5142                 rect_pos.y += g.FontSize;
   5143             }
   5144         }
   5145 
   5146         // 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.
   5147         if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
   5148         {
   5149             ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
   5150             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);
   5151         }
   5152 
   5153         // Draw blinking cursor
   5154         if (render_cursor)
   5155         {
   5156             state->CursorAnim += io.DeltaTime;
   5157             bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
   5158             ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
   5159             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);
   5160             if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
   5161                 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
   5162 
   5163             // 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.)
   5164             if (!is_readonly)
   5165             {
   5166                 g.PlatformImeData.WantVisible = true;
   5167                 g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
   5168                 g.PlatformImeData.InputLineHeight = g.FontSize;
   5169             }
   5170         }
   5171     }
   5172     else
   5173     {
   5174         // Render text only (no selection, no cursor)
   5175         if (is_multiline)
   5176             text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
   5177         else if (!is_displaying_hint && g.ActiveId == id)
   5178             buf_display_end = buf_display + state->CurLenA;
   5179         else if (!is_displaying_hint)
   5180             buf_display_end = buf_display + strlen(buf_display);
   5181 
   5182         if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
   5183         {
   5184             ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
   5185             draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
   5186         }
   5187     }
   5188 
   5189     if (is_password && !is_displaying_hint)
   5190         PopFont();
   5191 
   5192     if (is_multiline)
   5193     {
   5194         // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (ref issue #4761)...
   5195         Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
   5196         g.NextItemData.ItemFlags |= ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
   5197         EndChild();
   5198         item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
   5199 
   5200         // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active...
   5201         // FIXME: This quite messy/tricky, should attempt to get rid of the child window.
   5202         EndGroup();
   5203         if (g.LastItemData.ID == 0)
   5204         {
   5205             g.LastItemData.ID = id;
   5206             g.LastItemData.InFlags = item_data_backup.InFlags;
   5207             g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
   5208         }
   5209     }
   5210 
   5211     // Log as text
   5212     if (g.LogEnabled && (!is_password || is_displaying_hint))
   5213     {
   5214         LogSetNextTextDecoration("{", "}");
   5215         LogRenderedText(&draw_pos, buf_display, buf_display_end);
   5216     }
   5217 
   5218     if (label_size.x > 0)
   5219         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
   5220 
   5221     if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited))
   5222         MarkItemEdited(id);
   5223 
   5224     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
   5225     if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
   5226         return validated;
   5227     else
   5228         return value_changed;
   5229 }
   5230 
   5231 void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
   5232 {
   5233 #ifndef IMGUI_DISABLE_DEBUG_TOOLS
   5234     ImGuiContext& g = *GImGui;
   5235     ImStb::STB_TexteditState* stb_state = &state->Stb;
   5236     ImStb::StbUndoState* undo_state = &stb_state->undostate;
   5237     Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
   5238     DebugLocateItemOnHover(state->ID);
   5239     Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenW, state->CurLenA, stb_state->cursor, stb_state->select_start, stb_state->select_end);
   5240     Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x);
   5241     Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point);
   5242     if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY)) // Visualize undo state
   5243     {
   5244         PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
   5245         for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++)
   5246         {
   5247             ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];
   5248             const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' ';
   5249             if (undo_rec_type == ' ')
   5250                 BeginDisabled();
   5251             char buf[64] = "";
   5252             if (undo_rec_type != ' ' && undo_rec->char_storage != -1)
   5253                 ImTextStrToUtf8(buf, IM_ARRAYSIZE(buf), undo_state->undo_char + undo_rec->char_storage, undo_state->undo_char + undo_rec->char_storage + undo_rec->insert_length);
   5254             Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"",
   5255                 undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf);
   5256             if (undo_rec_type == ' ')
   5257                 EndDisabled();
   5258         }
   5259         PopStyleVar();
   5260     }
   5261     EndChild();
   5262 #else
   5263     IM_UNUSED(state);
   5264 #endif
   5265 }
   5266 
   5267 //-------------------------------------------------------------------------
   5268 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
   5269 //-------------------------------------------------------------------------
   5270 // - ColorEdit3()
   5271 // - ColorEdit4()
   5272 // - ColorPicker3()
   5273 // - RenderColorRectWithAlphaCheckerboard() [Internal]
   5274 // - ColorPicker4()
   5275 // - ColorButton()
   5276 // - SetColorEditOptions()
   5277 // - ColorTooltip() [Internal]
   5278 // - ColorEditOptionsPopup() [Internal]
   5279 // - ColorPickerOptionsPopup() [Internal]
   5280 //-------------------------------------------------------------------------
   5281 
   5282 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
   5283 {
   5284     return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
   5285 }
   5286 
   5287 static void ColorEditRestoreH(const float* col, float* H)
   5288 {
   5289     ImGuiContext& g = *GImGui;
   5290     IM_ASSERT(g.ColorEditCurrentID != 0);
   5291     if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
   5292         return;
   5293     *H = g.ColorEditSavedHue;
   5294 }
   5295 
   5296 // ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation.
   5297 // Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting.
   5298 static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
   5299 {
   5300     ImGuiContext& g = *GImGui;
   5301     IM_ASSERT(g.ColorEditCurrentID != 0);
   5302     if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
   5303         return;
   5304 
   5305     // When S == 0, H is undefined.
   5306     // When H == 1 it wraps around to 0.
   5307     if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
   5308         *H = g.ColorEditSavedHue;
   5309 
   5310     // When V == 0, S is undefined.
   5311     if (*V == 0.0f)
   5312         *S = g.ColorEditSavedSat;
   5313 }
   5314 
   5315 // Edit colors components (each component in 0.0f..1.0f range).
   5316 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
   5317 // 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.
   5318 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
   5319 {
   5320     ImGuiWindow* window = GetCurrentWindow();
   5321     if (window->SkipItems)
   5322         return false;
   5323 
   5324     ImGuiContext& g = *GImGui;
   5325     const ImGuiStyle& style = g.Style;
   5326     const float square_sz = GetFrameHeight();
   5327     const char* label_display_end = FindRenderedTextEnd(label);
   5328     float w_full = CalcItemWidth();
   5329     g.NextItemData.ClearFlags();
   5330 
   5331     BeginGroup();
   5332     PushID(label);
   5333     const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
   5334     if (set_current_color_edit_id)
   5335         g.ColorEditCurrentID = window->IDStack.back();
   5336 
   5337     // If we're not showing any slider there's no point in doing any HSV conversions
   5338     const ImGuiColorEditFlags flags_untouched = flags;
   5339     if (flags & ImGuiColorEditFlags_NoInputs)
   5340         flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
   5341 
   5342     // Context menu: display and modify options (before defaults are applied)
   5343     if (!(flags & ImGuiColorEditFlags_NoOptions))
   5344         ColorEditOptionsPopup(col, flags);
   5345 
   5346     // Read stored options
   5347     if (!(flags & ImGuiColorEditFlags_DisplayMask_))
   5348         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
   5349     if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
   5350         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
   5351     if (!(flags & ImGuiColorEditFlags_PickerMask_))
   5352         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
   5353     if (!(flags & ImGuiColorEditFlags_InputMask_))
   5354         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
   5355     flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
   5356     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
   5357     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));   // Check that only 1 is selected
   5358 
   5359     const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
   5360     const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
   5361     const int components = alpha ? 4 : 3;
   5362     const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
   5363     const float w_inputs = ImMax(w_full - w_button, 1.0f);
   5364     w_full = w_inputs + w_button;
   5365 
   5366     // Convert to the formats we need
   5367     float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
   5368     if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
   5369         ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
   5370     else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
   5371     {
   5372         // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
   5373         ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
   5374         ColorEditRestoreHS(col, &f[0], &f[1], &f[2]);
   5375     }
   5376     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]) };
   5377 
   5378     bool value_changed = false;
   5379     bool value_changed_as_float = false;
   5380 
   5381     const ImVec2 pos = window->DC.CursorPos;
   5382     const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
   5383     window->DC.CursorPos.x = pos.x + inputs_offset_x;
   5384 
   5385     if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
   5386     {
   5387         // RGB/HSV 0..255 Sliders
   5388         const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);
   5389 
   5390         const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
   5391         static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
   5392         static const char* fmt_table_int[3][4] =
   5393         {
   5394             {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
   5395             { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
   5396             { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
   5397         };
   5398         static const char* fmt_table_float[3][4] =
   5399         {
   5400             {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
   5401             { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
   5402             { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
   5403         };
   5404         const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
   5405 
   5406         float prev_split = 0.0f;
   5407         for (int n = 0; n < components; n++)
   5408         {
   5409             if (n > 0)
   5410                 SameLine(0, style.ItemInnerSpacing.x);
   5411             float next_split = IM_TRUNC(w_items * (n + 1) / components);
   5412             SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));
   5413             prev_split = next_split;
   5414 
   5415             // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
   5416             if (flags & ImGuiColorEditFlags_Float)
   5417             {
   5418                 value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
   5419                 value_changed_as_float |= value_changed;
   5420             }
   5421             else
   5422             {
   5423                 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
   5424             }
   5425             if (!(flags & ImGuiColorEditFlags_NoOptions))
   5426                 OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
   5427         }
   5428     }
   5429     else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
   5430     {
   5431         // RGB Hexadecimal Input
   5432         char buf[64];
   5433         if (alpha)
   5434             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));
   5435         else
   5436             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
   5437         SetNextItemWidth(w_inputs);
   5438         if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase))
   5439         {
   5440             value_changed = true;
   5441             char* p = buf;
   5442             while (*p == '#' || ImCharIsBlankA(*p))
   5443                 p++;
   5444             i[0] = i[1] = i[2] = 0;
   5445             i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
   5446             int r;
   5447             if (alpha)
   5448                 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)
   5449             else
   5450                 r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
   5451             IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
   5452         }
   5453         if (!(flags & ImGuiColorEditFlags_NoOptions))
   5454             OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
   5455     }
   5456 
   5457     ImGuiWindow* picker_active_window = NULL;
   5458     if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
   5459     {
   5460         const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
   5461         window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
   5462 
   5463         const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
   5464         if (ColorButton("##ColorButton", col_v4, flags))
   5465         {
   5466             if (!(flags & ImGuiColorEditFlags_NoPicker))
   5467             {
   5468                 // Store current color and open a picker
   5469                 g.ColorPickerRef = col_v4;
   5470                 OpenPopup("picker");
   5471                 SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));
   5472             }
   5473         }
   5474         if (!(flags & ImGuiColorEditFlags_NoOptions))
   5475             OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
   5476 
   5477         if (BeginPopup("picker"))
   5478         {
   5479             if (g.CurrentWindow->BeginCount == 1)
   5480             {
   5481                 picker_active_window = g.CurrentWindow;
   5482                 if (label != label_display_end)
   5483                 {
   5484                     TextEx(label, label_display_end);
   5485                     Spacing();
   5486                 }
   5487                 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
   5488                 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
   5489                 SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
   5490                 value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
   5491             }
   5492             EndPopup();
   5493         }
   5494     }
   5495 
   5496     if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
   5497     {
   5498         // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left),
   5499         // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this.
   5500         SameLine(0.0f, style.ItemInnerSpacing.x);
   5501         window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x);
   5502         TextEx(label, label_display_end);
   5503     }
   5504 
   5505     // Convert back
   5506     if (value_changed && picker_active_window == NULL)
   5507     {
   5508         if (!value_changed_as_float)
   5509             for (int n = 0; n < 4; n++)
   5510                 f[n] = i[n] / 255.0f;
   5511         if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
   5512         {
   5513             g.ColorEditSavedHue = f[0];
   5514             g.ColorEditSavedSat = f[1];
   5515             ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
   5516             g.ColorEditSavedID = g.ColorEditCurrentID;
   5517             g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));
   5518         }
   5519         if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
   5520             ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
   5521 
   5522         col[0] = f[0];
   5523         col[1] = f[1];
   5524         col[2] = f[2];
   5525         if (alpha)
   5526             col[3] = f[3];
   5527     }
   5528 
   5529     if (set_current_color_edit_id)
   5530         g.ColorEditCurrentID = 0;
   5531     PopID();
   5532     EndGroup();
   5533 
   5534     // Drag and Drop Target
   5535     // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
   5536     if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
   5537     {
   5538         bool accepted_drag_drop = false;
   5539         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
   5540         {
   5541             memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086
   5542             value_changed = accepted_drag_drop = true;
   5543         }
   5544         if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
   5545         {
   5546             memcpy((float*)col, payload->Data, sizeof(float) * components);
   5547             value_changed = accepted_drag_drop = true;
   5548         }
   5549 
   5550         // Drag-drop payloads are always RGB
   5551         if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
   5552             ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
   5553         EndDragDropTarget();
   5554     }
   5555 
   5556     // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
   5557     if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
   5558         g.LastItemData.ID = g.ActiveId;
   5559 
   5560     if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
   5561         MarkItemEdited(g.LastItemData.ID);
   5562 
   5563     return value_changed;
   5564 }
   5565 
   5566 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
   5567 {
   5568     float col4[4] = { col[0], col[1], col[2], 1.0f };
   5569     if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
   5570         return false;
   5571     col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
   5572     return true;
   5573 }
   5574 
   5575 // Helper for ColorPicker4()
   5576 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
   5577 {
   5578     ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
   5579     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));
   5580     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
   5581     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));
   5582     ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32(255,255,255,alpha8));
   5583 }
   5584 
   5585 // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
   5586 // (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.)
   5587 // 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..)
   5588 // 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)
   5589 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
   5590 {
   5591     ImGuiContext& g = *GImGui;
   5592     ImGuiWindow* window = GetCurrentWindow();
   5593     if (window->SkipItems)
   5594         return false;
   5595 
   5596     ImDrawList* draw_list = window->DrawList;
   5597     ImGuiStyle& style = g.Style;
   5598     ImGuiIO& io = g.IO;
   5599 
   5600     const float width = CalcItemWidth();
   5601     const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
   5602     g.NextItemData.ClearFlags();
   5603 
   5604     PushID(label);
   5605     const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
   5606     if (set_current_color_edit_id)
   5607         g.ColorEditCurrentID = window->IDStack.back();
   5608     BeginGroup();
   5609 
   5610     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
   5611         flags |= ImGuiColorEditFlags_NoSmallPreview;
   5612 
   5613     // Context menu: display and store options.
   5614     if (!(flags & ImGuiColorEditFlags_NoOptions))
   5615         ColorPickerOptionsPopup(col, flags);
   5616 
   5617     // Read stored options
   5618     if (!(flags & ImGuiColorEditFlags_PickerMask_))
   5619         flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
   5620     if (!(flags & ImGuiColorEditFlags_InputMask_))
   5621         flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
   5622     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
   5623     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));  // Check that only 1 is selected
   5624     if (!(flags & ImGuiColorEditFlags_NoOptions))
   5625         flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
   5626 
   5627     // Setup
   5628     int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
   5629     bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
   5630     ImVec2 picker_pos = window->DC.CursorPos;
   5631     float square_sz = GetFrameHeight();
   5632     float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
   5633     float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
   5634     float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
   5635     float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
   5636     float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f);
   5637 
   5638     float backup_initial_col[4];
   5639     memcpy(backup_initial_col, col, components * sizeof(float));
   5640 
   5641     float wheel_thickness = sv_picker_size * 0.08f;
   5642     float wheel_r_outer = sv_picker_size * 0.50f;
   5643     float wheel_r_inner = wheel_r_outer - wheel_thickness;
   5644     ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
   5645 
   5646     // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
   5647     float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
   5648     ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
   5649     ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
   5650     ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
   5651 
   5652     float H = col[0], S = col[1], V = col[2];
   5653     float R = col[0], G = col[1], B = col[2];
   5654     if (flags & ImGuiColorEditFlags_InputRGB)
   5655     {
   5656         // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
   5657         ColorConvertRGBtoHSV(R, G, B, H, S, V);
   5658         ColorEditRestoreHS(col, &H, &S, &V);
   5659     }
   5660     else if (flags & ImGuiColorEditFlags_InputHSV)
   5661     {
   5662         ColorConvertHSVtoRGB(H, S, V, R, G, B);
   5663     }
   5664 
   5665     bool value_changed = false, value_changed_h = false, value_changed_sv = false;
   5666 
   5667     PushItemFlag(ImGuiItemFlags_NoNav, true);
   5668     if (flags & ImGuiColorEditFlags_PickerHueWheel)
   5669     {
   5670         // Hue wheel + SV triangle logic
   5671         InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
   5672         if (IsItemActive() && !is_readonly)
   5673         {
   5674             ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
   5675             ImVec2 current_off = g.IO.MousePos - wheel_center;
   5676             float initial_dist2 = ImLengthSqr(initial_off);
   5677             if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
   5678             {
   5679                 // Interactive with Hue wheel
   5680                 H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
   5681                 if (H < 0.0f)
   5682                     H += 1.0f;
   5683                 value_changed = value_changed_h = true;
   5684             }
   5685             float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
   5686             float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
   5687             if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
   5688             {
   5689                 // Interacting with SV triangle
   5690                 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
   5691                 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
   5692                     current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
   5693                 float uu, vv, ww;
   5694                 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
   5695                 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
   5696                 S = ImClamp(uu / V, 0.0001f, 1.0f);
   5697                 value_changed = value_changed_sv = true;
   5698             }
   5699         }
   5700         if (!(flags & ImGuiColorEditFlags_NoOptions))
   5701             OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
   5702     }
   5703     else if (flags & ImGuiColorEditFlags_PickerHueBar)
   5704     {
   5705         // SV rectangle logic
   5706         InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
   5707         if (IsItemActive() && !is_readonly)
   5708         {
   5709             S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
   5710             V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
   5711             ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
   5712             value_changed = value_changed_sv = true;
   5713         }
   5714         if (!(flags & ImGuiColorEditFlags_NoOptions))
   5715             OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
   5716 
   5717         // Hue bar logic
   5718         SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
   5719         InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
   5720         if (IsItemActive() && !is_readonly)
   5721         {
   5722             H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
   5723             value_changed = value_changed_h = true;
   5724         }
   5725     }
   5726 
   5727     // Alpha bar logic
   5728     if (alpha_bar)
   5729     {
   5730         SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
   5731         InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
   5732         if (IsItemActive())
   5733         {
   5734             col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
   5735             value_changed = true;
   5736         }
   5737     }
   5738     PopItemFlag(); // ImGuiItemFlags_NoNav
   5739 
   5740     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
   5741     {
   5742         SameLine(0, style.ItemInnerSpacing.x);
   5743         BeginGroup();
   5744     }
   5745 
   5746     if (!(flags & ImGuiColorEditFlags_NoLabel))
   5747     {
   5748         const char* label_display_end = FindRenderedTextEnd(label);
   5749         if (label != label_display_end)
   5750         {
   5751             if ((flags & ImGuiColorEditFlags_NoSidePreview))
   5752                 SameLine(0, style.ItemInnerSpacing.x);
   5753             TextEx(label, label_display_end);
   5754         }
   5755     }
   5756 
   5757     if (!(flags & ImGuiColorEditFlags_NoSidePreview))
   5758     {
   5759         PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
   5760         ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
   5761         if ((flags & ImGuiColorEditFlags_NoLabel))
   5762             Text("Current");
   5763 
   5764         ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
   5765         ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
   5766         if (ref_col != NULL)
   5767         {
   5768             Text("Original");
   5769             ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
   5770             if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
   5771             {
   5772                 memcpy(col, ref_col, components * sizeof(float));
   5773                 value_changed = true;
   5774             }
   5775         }
   5776         PopItemFlag();
   5777         EndGroup();
   5778     }
   5779 
   5780     // Convert back color to RGB
   5781     if (value_changed_h || value_changed_sv)
   5782     {
   5783         if (flags & ImGuiColorEditFlags_InputRGB)
   5784         {
   5785             ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
   5786             g.ColorEditSavedHue = H;
   5787             g.ColorEditSavedSat = S;
   5788             g.ColorEditSavedID = g.ColorEditCurrentID;
   5789             g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));
   5790         }
   5791         else if (flags & ImGuiColorEditFlags_InputHSV)
   5792         {
   5793             col[0] = H;
   5794             col[1] = S;
   5795             col[2] = V;
   5796         }
   5797     }
   5798 
   5799     // R,G,B and H,S,V slider color editor
   5800     bool value_changed_fix_hue_wrap = false;
   5801     if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
   5802     {
   5803         PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
   5804         ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
   5805         ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
   5806         if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
   5807             if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
   5808             {
   5809                 // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
   5810                 // 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)
   5811                 value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
   5812                 value_changed = true;
   5813             }
   5814         if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
   5815             value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
   5816         if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
   5817             value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
   5818         PopItemWidth();
   5819     }
   5820 
   5821     // Try to cancel hue wrap (after ColorEdit4 call), if any
   5822     if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
   5823     {
   5824         float new_H, new_S, new_V;
   5825         ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
   5826         if (new_H <= 0 && H > 0)
   5827         {
   5828             if (new_V <= 0 && V != new_V)
   5829                 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
   5830             else if (new_S <= 0)
   5831                 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
   5832         }
   5833     }
   5834 
   5835     if (value_changed)
   5836     {
   5837         if (flags & ImGuiColorEditFlags_InputRGB)
   5838         {
   5839             R = col[0];
   5840             G = col[1];
   5841             B = col[2];
   5842             ColorConvertRGBtoHSV(R, G, B, H, S, V);
   5843             ColorEditRestoreHS(col, &H, &S, &V);   // Fix local Hue as display below will use it immediately.
   5844         }
   5845         else if (flags & ImGuiColorEditFlags_InputHSV)
   5846         {
   5847             H = col[0];
   5848             S = col[1];
   5849             V = col[2];
   5850             ColorConvertHSVtoRGB(H, S, V, R, G, B);
   5851         }
   5852     }
   5853 
   5854     const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
   5855     const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
   5856     const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
   5857     const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
   5858     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) };
   5859 
   5860     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);
   5861     ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
   5862     ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
   5863 
   5864     ImVec2 sv_cursor_pos;
   5865 
   5866     if (flags & ImGuiColorEditFlags_PickerHueWheel)
   5867     {
   5868         // Render Hue Wheel
   5869         const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
   5870         const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
   5871         for (int n = 0; n < 6; n++)
   5872         {
   5873             const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
   5874             const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
   5875             const int vert_start_idx = draw_list->VtxBuffer.Size;
   5876             draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
   5877             draw_list->PathStroke(col_white, 0, wheel_thickness);
   5878             const int vert_end_idx = draw_list->VtxBuffer.Size;
   5879 
   5880             // Paint colors over existing vertices
   5881             ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
   5882             ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
   5883             ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
   5884         }
   5885 
   5886         // Render Cursor + preview on Hue Wheel
   5887         float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
   5888         float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
   5889         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);
   5890         float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
   5891         int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others.
   5892         draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
   5893         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
   5894         draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
   5895 
   5896         // Render SV triangle (rotated according to hue)
   5897         ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
   5898         ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
   5899         ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
   5900         ImVec2 uv_white = GetFontTexUvWhitePixel();
   5901         draw_list->PrimReserve(3, 3);
   5902         draw_list->PrimVtx(tra, uv_white, hue_color32);
   5903         draw_list->PrimVtx(trb, uv_white, col_black);
   5904         draw_list->PrimVtx(trc, uv_white, col_white);
   5905         draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
   5906         sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
   5907     }
   5908     else if (flags & ImGuiColorEditFlags_PickerHueBar)
   5909     {
   5910         // Render SV Square
   5911         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
   5912         draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
   5913         RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
   5914         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
   5915         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);
   5916 
   5917         // Render Hue Bar
   5918         for (int i = 0; i < 6; ++i)
   5919             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]);
   5920         float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
   5921         RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
   5922         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);
   5923     }
   5924 
   5925     // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
   5926     float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f;
   5927     int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others.
   5928     draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments);
   5929     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments);
   5930     draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments);
   5931 
   5932     // Render alpha bar
   5933     if (alpha_bar)
   5934     {
   5935         float alpha = ImSaturate(col[3]);
   5936         ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
   5937         RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
   5938         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);
   5939         float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
   5940         RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
   5941         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);
   5942     }
   5943 
   5944     EndGroup();
   5945 
   5946     if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
   5947         value_changed = false;
   5948     if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
   5949         MarkItemEdited(g.LastItemData.ID);
   5950 
   5951     if (set_current_color_edit_id)
   5952         g.ColorEditCurrentID = 0;
   5953     PopID();
   5954 
   5955     return value_changed;
   5956 }
   5957 
   5958 // A little color square. Return true when clicked.
   5959 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
   5960 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
   5961 // Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
   5962 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
   5963 {
   5964     ImGuiWindow* window = GetCurrentWindow();
   5965     if (window->SkipItems)
   5966         return false;
   5967 
   5968     ImGuiContext& g = *GImGui;
   5969     const ImGuiID id = window->GetID(desc_id);
   5970     const float default_size = GetFrameHeight();
   5971     const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);
   5972     const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
   5973     ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
   5974     if (!ItemAdd(bb, id))
   5975         return false;
   5976 
   5977     bool hovered, held;
   5978     bool pressed = ButtonBehavior(bb, id, &hovered, &held);
   5979 
   5980     if (flags & ImGuiColorEditFlags_NoAlpha)
   5981         flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
   5982 
   5983     ImVec4 col_rgb = col;
   5984     if (flags & ImGuiColorEditFlags_InputHSV)
   5985         ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
   5986 
   5987     ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
   5988     float grid_step = ImMin(size.x, size.y) / 2.99f;
   5989     float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
   5990     ImRect bb_inner = bb;
   5991     float off = 0.0f;
   5992     if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
   5993     {
   5994         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.
   5995         bb_inner.Expand(off);
   5996     }
   5997     if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
   5998     {
   5999         float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
   6000         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);
   6001         window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
   6002     }
   6003     else
   6004     {
   6005         // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
   6006         ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
   6007         if (col_source.w < 1.0f)
   6008             RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
   6009         else
   6010             window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
   6011     }
   6012     RenderNavHighlight(bb, id);
   6013     if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
   6014     {
   6015         if (g.Style.FrameBorderSize > 0.0f)
   6016             RenderFrameBorder(bb.Min, bb.Max, rounding);
   6017         else
   6018             window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
   6019     }
   6020 
   6021     // Drag and Drop Source
   6022     // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
   6023     if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
   6024     {
   6025         if (flags & ImGuiColorEditFlags_NoAlpha)
   6026             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
   6027         else
   6028             SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
   6029         ColorButton(desc_id, col, flags);
   6030         SameLine();
   6031         TextEx("Color");
   6032         EndDragDropSource();
   6033     }
   6034 
   6035     // Tooltip
   6036     if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip))
   6037         ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
   6038 
   6039     return pressed;
   6040 }
   6041 
   6042 // Initialize/override default color options
   6043 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
   6044 {
   6045     ImGuiContext& g = *GImGui;
   6046     if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
   6047         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
   6048     if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
   6049         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
   6050     if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
   6051         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
   6052     if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
   6053         flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
   6054     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_));    // Check only 1 option is selected
   6055     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_));   // Check only 1 option is selected
   6056     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_));     // Check only 1 option is selected
   6057     IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));      // Check only 1 option is selected
   6058     g.ColorEditOptions = flags;
   6059 }
   6060 
   6061 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
   6062 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
   6063 {
   6064     ImGuiContext& g = *GImGui;
   6065 
   6066     if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
   6067         return;
   6068     const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
   6069     if (text_end > text)
   6070     {
   6071         TextEx(text, text_end);
   6072         Separator();
   6073     }
   6074 
   6075     ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
   6076     ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
   6077     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]);
   6078     ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
   6079     SameLine();
   6080     if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
   6081     {
   6082         if (flags & ImGuiColorEditFlags_NoAlpha)
   6083             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]);
   6084         else
   6085             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]);
   6086     }
   6087     else if (flags & ImGuiColorEditFlags_InputHSV)
   6088     {
   6089         if (flags & ImGuiColorEditFlags_NoAlpha)
   6090             Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
   6091         else
   6092             Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
   6093     }
   6094     EndTooltip();
   6095 }
   6096 
   6097 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
   6098 {
   6099     bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
   6100     bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
   6101     if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
   6102         return;
   6103     ImGuiContext& g = *GImGui;
   6104     g.LockMarkEdited++;
   6105     ImGuiColorEditFlags opts = g.ColorEditOptions;
   6106     if (allow_opt_inputs)
   6107     {
   6108         if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
   6109         if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
   6110         if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
   6111     }
   6112     if (allow_opt_datatype)
   6113     {
   6114         if (allow_opt_inputs) Separator();
   6115         if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
   6116         if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
   6117     }
   6118 
   6119     if (allow_opt_inputs || allow_opt_datatype)
   6120         Separator();
   6121     if (Button("Copy as..", ImVec2(-1, 0)))
   6122         OpenPopup("Copy");
   6123     if (BeginPopup("Copy"))
   6124     {
   6125         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]);
   6126         char buf[64];
   6127         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
   6128         if (Selectable(buf))
   6129             SetClipboardText(buf);
   6130         ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
   6131         if (Selectable(buf))
   6132             SetClipboardText(buf);
   6133         ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
   6134         if (Selectable(buf))
   6135             SetClipboardText(buf);
   6136         if (!(flags & ImGuiColorEditFlags_NoAlpha))
   6137         {
   6138             ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
   6139             if (Selectable(buf))
   6140                 SetClipboardText(buf);
   6141         }
   6142         EndPopup();
   6143     }
   6144 
   6145     g.ColorEditOptions = opts;
   6146     EndPopup();
   6147     g.LockMarkEdited--;
   6148 }
   6149 
   6150 void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
   6151 {
   6152     bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
   6153     bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
   6154     if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
   6155         return;
   6156     ImGuiContext& g = *GImGui;
   6157     g.LockMarkEdited++;
   6158     if (allow_opt_picker)
   6159     {
   6160         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
   6161         PushItemWidth(picker_size.x);
   6162         for (int picker_type = 0; picker_type < 2; picker_type++)
   6163         {
   6164             // Draw small/thumbnail version of each picker type (over an invisible button for selection)
   6165             if (picker_type > 0) Separator();
   6166             PushID(picker_type);
   6167             ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
   6168             if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
   6169             if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
   6170             ImVec2 backup_pos = GetCursorScreenPos();
   6171             if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
   6172                 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
   6173             SetCursorScreenPos(backup_pos);
   6174             ImVec4 previewing_ref_col;
   6175             memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
   6176             ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
   6177             PopID();
   6178         }
   6179         PopItemWidth();
   6180     }
   6181     if (allow_opt_alpha_bar)
   6182     {
   6183         if (allow_opt_picker) Separator();
   6184         CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
   6185     }
   6186     EndPopup();
   6187     g.LockMarkEdited--;
   6188 }
   6189 
   6190 //-------------------------------------------------------------------------
   6191 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
   6192 //-------------------------------------------------------------------------
   6193 // - TreeNode()
   6194 // - TreeNodeV()
   6195 // - TreeNodeEx()
   6196 // - TreeNodeExV()
   6197 // - TreeNodeBehavior() [Internal]
   6198 // - TreePush()
   6199 // - TreePop()
   6200 // - GetTreeNodeToLabelSpacing()
   6201 // - SetNextItemOpen()
   6202 // - CollapsingHeader()
   6203 //-------------------------------------------------------------------------
   6204 
   6205 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
   6206 {
   6207     va_list args;
   6208     va_start(args, fmt);
   6209     bool is_open = TreeNodeExV(str_id, 0, fmt, args);
   6210     va_end(args);
   6211     return is_open;
   6212 }
   6213 
   6214 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
   6215 {
   6216     va_list args;
   6217     va_start(args, fmt);
   6218     bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
   6219     va_end(args);
   6220     return is_open;
   6221 }
   6222 
   6223 bool ImGui::TreeNode(const char* label)
   6224 {
   6225     ImGuiWindow* window = GetCurrentWindow();
   6226     if (window->SkipItems)
   6227         return false;
   6228     ImGuiID id = window->GetID(label);
   6229     return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL);
   6230 }
   6231 
   6232 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
   6233 {
   6234     return TreeNodeExV(str_id, 0, fmt, args);
   6235 }
   6236 
   6237 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
   6238 {
   6239     return TreeNodeExV(ptr_id, 0, fmt, args);
   6240 }
   6241 
   6242 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
   6243 {
   6244     ImGuiWindow* window = GetCurrentWindow();
   6245     if (window->SkipItems)
   6246         return false;
   6247     ImGuiID id = window->GetID(label);
   6248     return TreeNodeBehavior(id, flags, label, NULL);
   6249 }
   6250 
   6251 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
   6252 {
   6253     va_list args;
   6254     va_start(args, fmt);
   6255     bool is_open = TreeNodeExV(str_id, flags, fmt, args);
   6256     va_end(args);
   6257     return is_open;
   6258 }
   6259 
   6260 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
   6261 {
   6262     va_list args;
   6263     va_start(args, fmt);
   6264     bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
   6265     va_end(args);
   6266     return is_open;
   6267 }
   6268 
   6269 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
   6270 {
   6271     ImGuiWindow* window = GetCurrentWindow();
   6272     if (window->SkipItems)
   6273         return false;
   6274 
   6275     ImGuiID id = window->GetID(str_id);
   6276     const char* label, *label_end;
   6277     ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
   6278     return TreeNodeBehavior(id, flags, label, label_end);
   6279 }
   6280 
   6281 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
   6282 {
   6283     ImGuiWindow* window = GetCurrentWindow();
   6284     if (window->SkipItems)
   6285         return false;
   6286 
   6287     ImGuiID id = window->GetID(ptr_id);
   6288     const char* label, *label_end;
   6289     ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
   6290     return TreeNodeBehavior(id, flags, label, label_end);
   6291 }
   6292 
   6293 bool ImGui::TreeNodeGetOpen(ImGuiID storage_id)
   6294 {
   6295     ImGuiContext& g = *GImGui;
   6296     ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
   6297     return storage->GetInt(storage_id, 0) != 0;
   6298 }
   6299 
   6300 void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open)
   6301 {
   6302     ImGuiContext& g = *GImGui;
   6303     ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
   6304     storage->SetInt(storage_id, open ? 1 : 0);
   6305 }
   6306 
   6307 bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags)
   6308 {
   6309     if (flags & ImGuiTreeNodeFlags_Leaf)
   6310         return true;
   6311 
   6312     // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function
   6313     ImGuiContext& g = *GImGui;
   6314     ImGuiWindow* window = g.CurrentWindow;
   6315     ImGuiStorage* storage = window->DC.StateStorage;
   6316 
   6317     bool is_open;
   6318     if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen)
   6319     {
   6320         if (g.NextItemData.OpenCond & ImGuiCond_Always)
   6321         {
   6322             is_open = g.NextItemData.OpenVal;
   6323             TreeNodeSetOpen(storage_id, is_open);
   6324         }
   6325         else
   6326         {
   6327             // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
   6328             const int stored_value = storage->GetInt(storage_id, -1);
   6329             if (stored_value == -1)
   6330             {
   6331                 is_open = g.NextItemData.OpenVal;
   6332                 TreeNodeSetOpen(storage_id, is_open);
   6333             }
   6334             else
   6335             {
   6336                 is_open = stored_value != 0;
   6337             }
   6338         }
   6339     }
   6340     else
   6341     {
   6342         is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
   6343     }
   6344 
   6345     // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
   6346     // NB- If we are above max depth we still allow manually opened nodes to be logged.
   6347     if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
   6348         is_open = true;
   6349 
   6350     return is_open;
   6351 }
   6352 
   6353 // Store ImGuiTreeNodeStackData for just submitted node.
   6354 // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
   6355 static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags)
   6356 {
   6357     ImGuiContext& g = *GImGui;
   6358     ImGuiWindow* window = g.CurrentWindow;
   6359 
   6360     g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);
   6361     ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back();
   6362     tree_node_data->ID = g.LastItemData.ID;
   6363     tree_node_data->TreeFlags = flags;
   6364     tree_node_data->InFlags = g.LastItemData.InFlags;
   6365     tree_node_data->NavRect = g.LastItemData.NavRect;
   6366     window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);
   6367 }
   6368 
   6369 // When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop.
   6370 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
   6371 {
   6372     ImGuiWindow* window = GetCurrentWindow();
   6373     if (window->SkipItems)
   6374         return false;
   6375 
   6376     ImGuiContext& g = *GImGui;
   6377     const ImGuiStyle& style = g.Style;
   6378     const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
   6379     const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
   6380 
   6381     if (!label_end)
   6382         label_end = FindRenderedTextEnd(label);
   6383     const ImVec2 label_size = CalcTextSize(label, label_end, false);
   6384 
   6385     const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2);   // Collapsing arrow width + Spacing
   6386     const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset);            // Latch before ItemSize changes it
   6387     const float text_width = g.FontSize + label_size.x + padding.x * 2;                         // Include collapsing arrow
   6388 
   6389     // We vertically grow up to current line height up the typical widget height.
   6390     const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
   6391     const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);
   6392     ImRect frame_bb;
   6393     frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
   6394     frame_bb.Min.y = window->DC.CursorPos.y;
   6395     frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanTextWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x;
   6396     frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
   6397     if (display_frame)
   6398     {
   6399         const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits
   6400         frame_bb.Min.x -= outer_extend;
   6401         frame_bb.Max.x += outer_extend;
   6402     }
   6403 
   6404     ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
   6405     ItemSize(ImVec2(text_width, frame_height), padding.y);
   6406 
   6407     // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
   6408     ImRect interact_bb = frame_bb;
   6409     if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0)
   6410         interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f);
   6411 
   6412     // Compute open and multi-select states before ItemAdd() as it clear NextItem data.
   6413     ImGuiID storage_id = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id;
   6414     bool is_open = TreeNodeUpdateNextOpen(storage_id, flags);
   6415 
   6416     bool is_visible;
   6417     if (span_all_columns)
   6418     {
   6419         // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
   6420         const float backup_clip_rect_min_x = window->ClipRect.Min.x;
   6421         const float backup_clip_rect_max_x = window->ClipRect.Max.x;
   6422         window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
   6423         window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
   6424         is_visible = ItemAdd(interact_bb, id);
   6425         window->ClipRect.Min.x = backup_clip_rect_min_x;
   6426         window->ClipRect.Max.x = backup_clip_rect_max_x;
   6427     }
   6428     else
   6429     {
   6430         is_visible = ItemAdd(interact_bb, id);
   6431     }
   6432     g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
   6433     g.LastItemData.DisplayRect = frame_bb;
   6434 
   6435     // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled:
   6436     // Store data for the current depth to allow returning to this node from any child item.
   6437     // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
   6438     // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle.
   6439     bool store_tree_node_stack_data = false;
   6440     if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
   6441     {
   6442         if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive)
   6443             if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
   6444                 store_tree_node_stack_data = true;
   6445     }
   6446 
   6447     const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
   6448     if (!is_visible)
   6449     {
   6450         if (store_tree_node_stack_data && is_open)
   6451             TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
   6452         if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
   6453             TreePushOverrideID(id);
   6454         IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
   6455         return is_open;
   6456     }
   6457 
   6458     if (span_all_columns)
   6459     {
   6460         TablePushBackgroundChannel();
   6461         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
   6462         g.LastItemData.ClipRect = window->ClipRect;
   6463     }
   6464 
   6465     ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
   6466     if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap))
   6467         button_flags |= ImGuiButtonFlags_AllowOverlap;
   6468     if (!is_leaf)
   6469         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
   6470 
   6471     // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
   6472     // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
   6473     // When clicking on the rest of the tree node we always disallow keyboard modifiers.
   6474     const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
   6475     const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
   6476     const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
   6477 
   6478     // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
   6479     // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
   6480     // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
   6481     // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
   6482     // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
   6483     // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
   6484     // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
   6485     // It is rather standard that arrow click react on Down rather than Up.
   6486     // 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.
   6487     if (is_mouse_x_over_arrow)
   6488         button_flags |= ImGuiButtonFlags_PressedOnClick;
   6489     else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
   6490         button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
   6491     else
   6492         button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
   6493 
   6494     bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
   6495     const bool was_selected = selected;
   6496 
   6497     // Multi-selection support (header)
   6498     const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0;
   6499     if (is_multi_select)
   6500     {
   6501         // Handle multi-select + alter button flags for it
   6502         MultiSelectItemHeader(id, &selected, &button_flags);
   6503         if (is_mouse_x_over_arrow)
   6504             button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
   6505 
   6506         // We absolutely need to distinguish open vs select so comes by default
   6507         flags |= ImGuiTreeNodeFlags_OpenOnArrow;
   6508     }
   6509     else
   6510     {
   6511         if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
   6512             button_flags |= ImGuiButtonFlags_NoKeyModifiers;
   6513     }
   6514 
   6515     bool hovered, held;
   6516     bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
   6517     bool toggled = false;
   6518     if (!is_leaf)
   6519     {
   6520         if (pressed && g.DragDropHoldJustPressedId != id)
   6521         {
   6522             if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id && !is_multi_select))
   6523                 toggled = true;
   6524             if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
   6525                 toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
   6526             if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2)
   6527                 toggled = true;
   6528         }
   6529         else if (pressed && g.DragDropHoldJustPressedId == id)
   6530         {
   6531             IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
   6532             if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
   6533                 toggled = true;
   6534         }
   6535 
   6536         if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)
   6537         {
   6538             toggled = true;
   6539             NavClearPreferredPosForAxis(ImGuiAxis_X);
   6540             NavMoveRequestCancel();
   6541         }
   6542         if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
   6543         {
   6544             toggled = true;
   6545             NavClearPreferredPosForAxis(ImGuiAxis_X);
   6546             NavMoveRequestCancel();
   6547         }
   6548 
   6549         if (toggled)
   6550         {
   6551             is_open = !is_open;
   6552             window->DC.StateStorage->SetInt(storage_id, is_open);
   6553             g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
   6554         }
   6555     }
   6556 
   6557     // Multi-selection support (footer)
   6558     if (is_multi_select)
   6559     {
   6560         bool pressed_copy = pressed && !toggled;
   6561         MultiSelectItemFooter(id, &selected, &pressed_copy);
   6562         if (pressed)
   6563             SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);
   6564     }
   6565 
   6566     if (selected != was_selected)
   6567         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
   6568 
   6569     // Render
   6570     {
   6571         const ImU32 text_col = GetColorU32(ImGuiCol_Text);
   6572         ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact;
   6573         if (is_multi_select)
   6574             nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle
   6575         if (display_frame)
   6576         {
   6577             // Framed type
   6578             const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
   6579             RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
   6580             RenderNavHighlight(frame_bb, id, nav_highlight_flags);
   6581             if (flags & ImGuiTreeNodeFlags_Bullet)
   6582                 RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
   6583             else if (!is_leaf)
   6584                 RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f);
   6585             else // Leaf without bullet, left-adjusted text
   6586                 text_pos.x -= text_offset_x - padding.x;
   6587             if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
   6588                 frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
   6589             if (g.LogEnabled)
   6590                 LogSetNextTextDecoration("###", "###");
   6591         }
   6592         else
   6593         {
   6594             // Unframed typed for tree nodes
   6595             if (hovered || selected)
   6596             {
   6597                 const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
   6598                 RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
   6599             }
   6600             RenderNavHighlight(frame_bb, id, nav_highlight_flags);
   6601             if (flags & ImGuiTreeNodeFlags_Bullet)
   6602                 RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
   6603             else if (!is_leaf)
   6604                 RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f);
   6605             if (g.LogEnabled)
   6606                 LogSetNextTextDecoration(">", NULL);
   6607         }
   6608 
   6609         if (span_all_columns)
   6610             TablePopBackgroundChannel();
   6611 
   6612         // Label
   6613         if (display_frame)
   6614             RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
   6615         else
   6616             RenderText(text_pos, label, label_end, false);
   6617     }
   6618 
   6619     if (store_tree_node_stack_data && is_open)
   6620         TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
   6621     if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
   6622         TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice
   6623 
   6624     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
   6625     return is_open;
   6626 }
   6627 
   6628 void ImGui::TreePush(const char* str_id)
   6629 {
   6630     ImGuiWindow* window = GetCurrentWindow();
   6631     Indent();
   6632     window->DC.TreeDepth++;
   6633     PushID(str_id);
   6634 }
   6635 
   6636 void ImGui::TreePush(const void* ptr_id)
   6637 {
   6638     ImGuiWindow* window = GetCurrentWindow();
   6639     Indent();
   6640     window->DC.TreeDepth++;
   6641     PushID(ptr_id);
   6642 }
   6643 
   6644 void ImGui::TreePushOverrideID(ImGuiID id)
   6645 {
   6646     ImGuiContext& g = *GImGui;
   6647     ImGuiWindow* window = g.CurrentWindow;
   6648     Indent();
   6649     window->DC.TreeDepth++;
   6650     PushOverrideID(id);
   6651 }
   6652 
   6653 void ImGui::TreePop()
   6654 {
   6655     ImGuiContext& g = *GImGui;
   6656     ImGuiWindow* window = g.CurrentWindow;
   6657     Unindent();
   6658 
   6659     window->DC.TreeDepth--;
   6660     ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
   6661 
   6662     if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request
   6663     {
   6664         ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back();
   6665         IM_ASSERT(data->ID == window->IDStack.back());
   6666         if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere)
   6667         {
   6668             // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
   6669             if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
   6670                 NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);
   6671         }
   6672         g.TreeNodeStack.pop_back();
   6673         window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;
   6674     }
   6675 
   6676     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.
   6677     PopID();
   6678 }
   6679 
   6680 // Horizontal distance preceding label when using TreeNode() or Bullet()
   6681 float ImGui::GetTreeNodeToLabelSpacing()
   6682 {
   6683     ImGuiContext& g = *GImGui;
   6684     return g.FontSize + (g.Style.FramePadding.x * 2.0f);
   6685 }
   6686 
   6687 // Set next TreeNode/CollapsingHeader open state.
   6688 void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
   6689 {
   6690     ImGuiContext& g = *GImGui;
   6691     if (g.CurrentWindow->SkipItems)
   6692         return;
   6693     g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen;
   6694     g.NextItemData.OpenVal = is_open;
   6695     g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always);
   6696 }
   6697 
   6698 // Set next TreeNode/CollapsingHeader storage id.
   6699 void ImGui::SetNextItemStorageID(ImGuiID storage_id)
   6700 {
   6701     ImGuiContext& g = *GImGui;
   6702     if (g.CurrentWindow->SkipItems)
   6703         return;
   6704     g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasStorageID;
   6705     g.NextItemData.StorageId = storage_id;
   6706 }
   6707 
   6708 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
   6709 // 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().
   6710 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
   6711 {
   6712     ImGuiWindow* window = GetCurrentWindow();
   6713     if (window->SkipItems)
   6714         return false;
   6715     ImGuiID id = window->GetID(label);
   6716     return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
   6717 }
   6718 
   6719 // p_visible == NULL                        : regular collapsing header
   6720 // 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
   6721 // p_visible != NULL && *p_visible == false : do not show the header at all
   6722 // Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
   6723 bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
   6724 {
   6725     ImGuiWindow* window = GetCurrentWindow();
   6726     if (window->SkipItems)
   6727         return false;
   6728 
   6729     if (p_visible && !*p_visible)
   6730         return false;
   6731 
   6732     ImGuiID id = window->GetID(label);
   6733     flags |= ImGuiTreeNodeFlags_CollapsingHeader;
   6734     if (p_visible)
   6735         flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
   6736     bool is_open = TreeNodeBehavior(id, flags, label);
   6737     if (p_visible != NULL)
   6738     {
   6739         // Create a small overlapping close button
   6740         // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
   6741         // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
   6742         ImGuiContext& g = *GImGui;
   6743         ImGuiLastItemData last_item_backup = g.LastItemData;
   6744         float button_size = g.FontSize;
   6745         float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size);
   6746         float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;
   6747         ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
   6748         if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
   6749             *p_visible = false;
   6750         g.LastItemData = last_item_backup;
   6751     }
   6752 
   6753     return is_open;
   6754 }
   6755 
   6756 //-------------------------------------------------------------------------
   6757 // [SECTION] Widgets: Selectable
   6758 //-------------------------------------------------------------------------
   6759 // - Selectable()
   6760 //-------------------------------------------------------------------------
   6761 
   6762 // Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
   6763 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
   6764 // With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.
   6765 // FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
   6766 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
   6767 {
   6768     ImGuiWindow* window = GetCurrentWindow();
   6769     if (window->SkipItems)
   6770         return false;
   6771 
   6772     ImGuiContext& g = *GImGui;
   6773     const ImGuiStyle& style = g.Style;
   6774 
   6775     // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
   6776     ImGuiID id = window->GetID(label);
   6777     ImVec2 label_size = CalcTextSize(label, NULL, true);
   6778     ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
   6779     ImVec2 pos = window->DC.CursorPos;
   6780     pos.y += window->DC.CurrLineTextBaseOffset;
   6781     ItemSize(size, 0.0f);
   6782 
   6783     // Fill horizontal space
   6784     // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
   6785     const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
   6786     const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
   6787     const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
   6788     if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
   6789         size.x = ImMax(label_size.x, max_x - min_x);
   6790 
   6791     // Text stays at the submission position, but bounding box may be extended on both sides
   6792     const ImVec2 text_min = pos;
   6793     const ImVec2 text_max(min_x + size.x, pos.y + size.y);
   6794 
   6795     // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
   6796     // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos.
   6797     ImRect bb(min_x, pos.y, text_max.x, text_max.y);
   6798     if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
   6799     {
   6800         const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
   6801         const float spacing_y = style.ItemSpacing.y;
   6802         const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
   6803         const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
   6804         bb.Min.x -= spacing_L;
   6805         bb.Min.y -= spacing_U;
   6806         bb.Max.x += (spacing_x - spacing_L);
   6807         bb.Max.y += (spacing_y - spacing_U);
   6808     }
   6809     //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
   6810 
   6811     const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
   6812     const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None;
   6813     bool is_visible;
   6814     if (span_all_columns)
   6815     {
   6816         // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
   6817         const float backup_clip_rect_min_x = window->ClipRect.Min.x;
   6818         const float backup_clip_rect_max_x = window->ClipRect.Max.x;
   6819         window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
   6820         window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
   6821         is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
   6822         window->ClipRect.Min.x = backup_clip_rect_min_x;
   6823         window->ClipRect.Max.x = backup_clip_rect_max_x;
   6824     }
   6825     else
   6826     {
   6827         is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
   6828     }
   6829 
   6830     const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0;
   6831     if (!is_visible)
   6832         if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd)
   6833             return false;
   6834 
   6835     const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
   6836     if (disabled_item && !disabled_global) // Only testing this as an optimization
   6837         BeginDisabled();
   6838 
   6839     // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
   6840     // which would be advantageous since most selectable are not selected.
   6841     if (span_all_columns)
   6842     {
   6843         if (g.CurrentTable)
   6844             TablePushBackgroundChannel();
   6845         else if (window->DC.CurrentColumns)
   6846             PushColumnsBackground();
   6847         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
   6848         g.LastItemData.ClipRect = window->ClipRect;
   6849     }
   6850 
   6851     // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
   6852     ImGuiButtonFlags button_flags = 0;
   6853     if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
   6854     if (flags & ImGuiSelectableFlags_NoSetKeyOwner)     { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; }
   6855     if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }
   6856     if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
   6857     if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
   6858     if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
   6859 
   6860     // Multi-selection support (header)
   6861     const bool was_selected = selected;
   6862     if (is_multi_select)
   6863     {
   6864         // Handle multi-select + alter button flags for it
   6865         MultiSelectItemHeader(id, &selected, &button_flags);
   6866     }
   6867 
   6868     bool hovered, held;
   6869     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
   6870 
   6871     // Multi-selection support (footer)
   6872     if (is_multi_select)
   6873     {
   6874         MultiSelectItemFooter(id, &selected, &pressed);
   6875     }
   6876     else
   6877     {
   6878         // Auto-select when moved into
   6879         // - This will be more fully fleshed in the range-select branch
   6880         // - This is not exposed as it won't nicely work with some user side handling of shift/control
   6881         // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
   6882         //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
   6883         //   - (2) usage will fail with clipped items
   6884         //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
   6885         if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
   6886             if (g.NavJustMovedToId == id)
   6887                 selected = pressed = true;
   6888     }
   6889 
   6890     // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
   6891     if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
   6892     {
   6893         if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
   6894         {
   6895             SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)
   6896             g.NavDisableHighlight = true;
   6897         }
   6898     }
   6899     if (pressed)
   6900         MarkItemEdited(id);
   6901 
   6902     if (selected != was_selected)
   6903         g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
   6904 
   6905     // Render
   6906     if (is_visible)
   6907     {
   6908         const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight);
   6909         if (highlighted || selected)
   6910         {
   6911             // FIXME-MULTISELECT: Styling: Color for 'selected' elements? ImGuiCol_HeaderSelected
   6912             ImU32 col;
   6913             if (selected && !highlighted)
   6914                 col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f));
   6915             else
   6916                 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
   6917             RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
   6918         }
   6919         if (g.NavId == id)
   6920         {
   6921             ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding;
   6922             if (is_multi_select)
   6923                 nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle
   6924             RenderNavHighlight(bb, id, nav_highlight_flags);
   6925         }
   6926     }
   6927 
   6928     if (span_all_columns)
   6929     {
   6930         if (g.CurrentTable)
   6931             TablePopBackgroundChannel();
   6932         else if (window->DC.CurrentColumns)
   6933             PopColumnsBackground();
   6934     }
   6935 
   6936     if (is_visible)
   6937         RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
   6938 
   6939     // Automatically close popups
   6940     if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.InFlags & ImGuiItemFlags_AutoClosePopups))
   6941         CloseCurrentPopup();
   6942 
   6943     if (disabled_item && !disabled_global)
   6944         EndDisabled();
   6945 
   6946     // Selectable() always returns a pressed state!
   6947     // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve
   6948     // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect().
   6949     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
   6950     return pressed; //-V1020
   6951 }
   6952 
   6953 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
   6954 {
   6955     if (Selectable(label, *p_selected, flags, size_arg))
   6956     {
   6957         *p_selected = !*p_selected;
   6958         return true;
   6959     }
   6960     return false;
   6961 }
   6962 
   6963 
   6964 //-------------------------------------------------------------------------
   6965 // [SECTION] Widgets: Typing-Select support
   6966 //-------------------------------------------------------------------------
   6967 
   6968 // [Experimental] Currently not exposed in public API.
   6969 // Consume character inputs and return search request, if any.
   6970 // This would typically only be called on the focused window or location you want to grab inputs for, e.g.
   6971 //   if (ImGui::IsWindowFocused(...))
   6972 //       if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest())
   6973 //           focus_idx = ImGui::TypingSelectFindMatch(req, my_items.size(), [](void*, int n) { return my_items[n]->Name; }, &my_items, -1);
   6974 // However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer).
   6975 ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags)
   6976 {
   6977     ImGuiContext& g = *GImGui;
   6978     ImGuiTypingSelectState* data = &g.TypingSelectState;
   6979     ImGuiTypingSelectRequest* out_request = &data->Request;
   6980 
   6981     // Clear buffer
   6982     const float TYPING_SELECT_RESET_TIMER = 1.80f;          // FIXME: Potentially move to IO config.
   6983     const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times
   6984     if (data->SearchBuffer[0] != 0)
   6985     {
   6986         bool clear_buffer = false;
   6987         clear_buffer |= (g.NavFocusScopeId != data->FocusScope);
   6988         clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time);
   6989         clear_buffer |= g.NavAnyRequest;
   6990         clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere
   6991         clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter);
   6992         clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0;
   6993         //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); }
   6994         if (clear_buffer)
   6995             data->Clear();
   6996     }
   6997 
   6998     // Append to buffer
   6999     const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;
   7000     int buffer_len = (int)strlen(data->SearchBuffer);
   7001     bool select_request = false;
   7002     for (ImWchar w : g.IO.InputQueueCharacters)
   7003     {
   7004         const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1);
   7005         if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w)) || (buffer_len + w_len > buffer_max_len)) // Ignore leading blanks
   7006             continue;
   7007         char w_buf[5];
   7008         ImTextCharToUtf8(w_buf, (unsigned int)w);
   7009         if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0)
   7010         {
   7011             select_request = true; // Same character: don't need to append to buffer.
   7012             continue;
   7013         }
   7014         if (data->SingleCharModeLock)
   7015         {
   7016             data->Clear(); // Different character: clear
   7017             buffer_len = 0;
   7018         }
   7019         memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append
   7020         buffer_len += w_len;
   7021         select_request = true;
   7022     }
   7023     g.IO.InputQueueCharacters.resize(0);
   7024 
   7025     // Handle backspace
   7026     if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat))
   7027     {
   7028         char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len);
   7029         *p = 0;
   7030         buffer_len = (int)(p - data->SearchBuffer);
   7031     }
   7032 
   7033     // Return request if any
   7034     if (buffer_len == 0)
   7035         return NULL;
   7036     if (select_request)
   7037     {
   7038         data->FocusScope = g.NavFocusScopeId;
   7039         data->LastRequestFrame = g.FrameCount;
   7040         data->LastRequestTime = (float)g.Time;
   7041     }
   7042     out_request->Flags = flags;
   7043     out_request->SearchBufferLen = buffer_len;
   7044     out_request->SearchBuffer = data->SearchBuffer;
   7045     out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount);
   7046     out_request->SingleCharMode = false;
   7047     out_request->SingleCharSize = 0;
   7048 
   7049     // Calculate if buffer contains the same character repeated.
   7050     // - This can be used to implement a special search mode on first character.
   7051     // - Performed on UTF-8 codepoint for correctness.
   7052     // - SingleCharMode is always set for first input character, because it usually leads to a "next".
   7053     if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode)
   7054     {
   7055         const char* buf_begin = out_request->SearchBuffer;
   7056         const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen;
   7057         const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end);
   7058         const char* p = buf_begin + c0_len;
   7059         for (; p < buf_end; p += c0_len)
   7060             if (memcmp(buf_begin, p, (size_t)c0_len) != 0)
   7061                 break;
   7062         const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0;
   7063         out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock);
   7064         out_request->SingleCharSize = (ImS8)c0_len;
   7065         data->SingleCharModeLock |= (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK); // From now on we stop search matching to lock to single char mode.
   7066     }
   7067 
   7068     return out_request;
   7069 }
   7070 
   7071 static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2)
   7072 {
   7073     int match_len = 0;
   7074     while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++))
   7075         match_len++;
   7076     return match_len;
   7077 }
   7078 
   7079 // Default handler for finding a result for typing-select. You may implement your own.
   7080 // You might want to display a tooltip to visualize the current request SearchBuffer
   7081 // When SingleCharMode is set:
   7082 // - it is better to NOT display a tooltip of other on-screen display indicator.
   7083 // - the index of the currently focused item is required.
   7084 //   if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData.
   7085 int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
   7086 {
   7087     if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot.
   7088         return -1;
   7089     int idx = -1;
   7090     if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode))
   7091         idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx);
   7092     else
   7093         idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data);
   7094     if (idx != -1)
   7095         NavRestoreHighlightAfterMove();
   7096     return idx;
   7097 }
   7098 
   7099 // Special handling when a single character is repeated: perform search on a single letter and goes to next.
   7100 int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
   7101 {
   7102     // FIXME: Assume selection user data is index. Would be extremely practical.
   7103     //if (nav_item_idx == -1)
   7104     //    nav_item_idx = (int)g.NavLastValidSelectionUserData;
   7105 
   7106     int first_match_idx = -1;
   7107     bool return_next_match = false;
   7108     for (int idx = 0; idx < items_count; idx++)
   7109     {
   7110         const char* item_name = get_item_name_func(user_data, idx);
   7111         if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize)
   7112             continue;
   7113         if (return_next_match)                           // Return next matching item after current item.
   7114             return idx;
   7115         if (first_match_idx == -1 && nav_item_idx == -1) // Return first match immediately if we don't have a nav_item_idx value.
   7116             return idx;
   7117         if (first_match_idx == -1)                       // Record first match for wrapping.
   7118             first_match_idx = idx;
   7119         if (nav_item_idx == idx)                         // Record that we encountering nav_item so we can return next match.
   7120             return_next_match = true;
   7121     }
   7122     return first_match_idx; // First result
   7123 }
   7124 
   7125 int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data)
   7126 {
   7127     int longest_match_idx = -1;
   7128     int longest_match_len = 0;
   7129     for (int idx = 0; idx < items_count; idx++)
   7130     {
   7131         const char* item_name = get_item_name_func(user_data, idx);
   7132         const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name);
   7133         if (match_len <= longest_match_len)
   7134             continue;
   7135         longest_match_idx = idx;
   7136         longest_match_len = match_len;
   7137         if (match_len == req->SearchBufferLen)
   7138             break;
   7139     }
   7140     return longest_match_idx;
   7141 }
   7142 
   7143 void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
   7144 {
   7145 #ifndef IMGUI_DISABLE_DEBUG_TOOLS
   7146     Text("SearchBuffer = \"%s\"", data->SearchBuffer);
   7147     Text("SingleCharMode = %d, Size = %d, Lock = %d", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock);
   7148     Text("LastRequest = time: %.2f, frame: %d", data->LastRequestTime, data->LastRequestFrame);
   7149 #else
   7150     IM_UNUSED(data);
   7151 #endif
   7152 }
   7153 
   7154 //-------------------------------------------------------------------------
   7155 // [SECTION] Widgets: Box-Select support
   7156 // This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet.
   7157 //-------------------------------------------------------------------------
   7158 // Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step()
   7159 //-------------------------------------------------------------------------
   7160 // - BoxSelectPreStartDrag() [Internal]
   7161 // - BoxSelectActivateDrag() [Internal]
   7162 // - BoxSelectDeactivateDrag() [Internal]
   7163 // - BoxSelectScrollWithMouseDrag() [Internal]
   7164 // - BeginBoxSelect() [Internal]
   7165 // - EndBoxSelect() [Internal]
   7166 //-------------------------------------------------------------------------
   7167 
   7168 // Call on the initial click.
   7169 static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item)
   7170 {
   7171     ImGuiContext& g = *GImGui;
   7172     ImGuiBoxSelectState* bs = &g.BoxSelectState;
   7173     bs->ID = id;
   7174     bs->IsStarting = true; // Consider starting box-select.
   7175     bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid);
   7176     bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid;
   7177     bs->KeyMods = g.IO.KeyMods;
   7178     bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);
   7179     bs->ScrollAccum = ImVec2(0.0f, 0.0f);
   7180 }
   7181 
   7182 static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window)
   7183 {
   7184     ImGuiContext& g = *GImGui;
   7185     IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID);
   7186     bs->IsActive = true;
   7187     bs->Window = window;
   7188     bs->IsStarting = false;
   7189     ImGui::SetActiveID(bs->ID, window);
   7190     ImGui::SetActiveIdUsingAllKeyboardKeys();
   7191     if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)
   7192         bs->RequestClear = true;
   7193 }
   7194 
   7195 static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs)
   7196 {
   7197     ImGuiContext& g = *GImGui;
   7198     bs->IsActive = bs->IsStarting = false;
   7199     if (g.ActiveId == bs->ID)
   7200     {
   7201         IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID);
   7202         ImGui::ClearActiveID();
   7203     }
   7204     bs->ID = 0;
   7205 }
   7206 
   7207 static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r)
   7208 {
   7209     ImGuiContext& g = *GImGui;
   7210     IM_ASSERT(bs->Window == window);
   7211     for (int n = 0; n < 2; n++) // each axis
   7212     {
   7213         const float mouse_pos = g.IO.MousePos[n];
   7214         const float dist = (mouse_pos > inner_r.Max[n]) ? mouse_pos - inner_r.Max[n] : (mouse_pos < inner_r.Min[n]) ? mouse_pos - inner_r.Min[n] : 0.0f;
   7215         const float scroll_curr = window->Scroll[n];
   7216         if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n]))
   7217             continue;
   7218 
   7219         const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance
   7220         const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime;
   7221         bs->ScrollAccum[n] += scroll_step;
   7222 
   7223         // Accumulate into a stored value so we can handle high-framerate
   7224         const float scroll_step_i = ImFloor(bs->ScrollAccum[n]);
   7225         if (scroll_step_i == 0.0f)
   7226             continue;
   7227         if (n == 0)
   7228             ImGui::SetScrollX(window, scroll_curr + scroll_step_i);
   7229         else
   7230             ImGui::SetScrollY(window, scroll_curr + scroll_step_i);
   7231         bs->ScrollAccum[n] -= scroll_step_i;
   7232     }
   7233 }
   7234 
   7235 bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags)
   7236 {
   7237     ImGuiContext& g = *GImGui;
   7238     ImGuiBoxSelectState* bs = &g.BoxSelectState;
   7239     KeepAliveID(box_select_id);
   7240     if (bs->ID != box_select_id)
   7241         return false;
   7242 
   7243     // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry.
   7244     bs->UnclipMode = false;
   7245     bs->RequestClear = false;
   7246     if (bs->IsStarting && IsMouseDragPastThreshold(0))
   7247         BoxSelectActivateDrag(bs, window);
   7248     else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)
   7249         BoxSelectDeactivateDrag(bs);
   7250     if (!bs->IsActive)
   7251         return false;
   7252 
   7253     // Current frame absolute prev/current rectangles are used to toggle selection.
   7254     // They are derived from positions relative to scrolling space.
   7255     ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
   7256     ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
   7257     ImVec2 curr_end_pos_abs = g.IO.MousePos;
   7258     if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow
   7259         curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max);
   7260     bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);
   7261     bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
   7262     bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
   7263     bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
   7264 
   7265     // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)
   7266     // Storing an extra rect used by widgets supporting box-select.
   7267     if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
   7268         if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
   7269         {
   7270             bs->UnclipMode = true;
   7271             bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis.
   7272             bs->UnclipRect.Add(bs->BoxSelectRectCurr);
   7273         }
   7274 
   7275     //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
   7276     //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
   7277     //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
   7278     return true;
   7279 }
   7280 
   7281 void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags)
   7282 {
   7283     ImGuiContext& g = *GImGui;
   7284     ImGuiWindow* window = g.CurrentWindow;
   7285     ImGuiBoxSelectState* bs = &g.BoxSelectState;
   7286     IM_ASSERT(bs->IsActive);
   7287     bs->UnclipMode = false;
   7288 
   7289     // Render selection rectangle
   7290     bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view
   7291     ImRect box_select_r = bs->BoxSelectRectCurr;
   7292     box_select_r.ClipWith(scope_rect);
   7293     window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
   7294     window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavHighlight)); // FIXME-MULTISELECT: Styling
   7295 
   7296     // Scroll
   7297     const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
   7298     if (enable_scroll)
   7299     {
   7300         ImRect scroll_r = scope_rect;
   7301         scroll_r.Expand(-g.FontSize);
   7302         //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));
   7303         if (!scroll_r.Contains(g.IO.MousePos))
   7304             BoxSelectScrollWithMouseDrag(bs, window, scroll_r);
   7305     }
   7306 }
   7307 
   7308 //-------------------------------------------------------------------------
   7309 // [SECTION] Widgets: Multi-Select support
   7310 //-------------------------------------------------------------------------
   7311 // - DebugLogMultiSelectRequests() [Internal]
   7312 // - CalcScopeRect() [Internal]
   7313 // - BeginMultiSelect()
   7314 // - EndMultiSelect()
   7315 // - SetNextItemSelectionUserData()
   7316 // - MultiSelectItemHeader() [Internal]
   7317 // - MultiSelectItemFooter() [Internal]
   7318 // - DebugNodeMultiSelectState() [Internal]
   7319 //-------------------------------------------------------------------------
   7320 
   7321 static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io)
   7322 {
   7323     ImGuiContext& g = *GImGui;
   7324     for (const ImGuiSelectionRequest& req : io->Requests)
   7325     {
   7326         if (req.Type == ImGuiSelectionRequestType_SetAll)    IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear");
   7327         if (req.Type == ImGuiSelectionRequestType_SetRange)  IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d (dir %d)\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected, req.RangeDirection);
   7328     }
   7329 }
   7330 
   7331 static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window)
   7332 {
   7333     if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
   7334     {
   7335         // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only
   7336         return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin));
   7337     }
   7338     else
   7339     {
   7340         // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect?
   7341         ImRect scope_rect = window->InnerClipRect;
   7342         scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max);
   7343         return scope_rect;
   7344     }
   7345 }
   7346 
   7347 // Return ImGuiMultiSelectIO structure.
   7348 // Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
   7349 // Passing 'selection_size' and 'items_count' parameters is currently optional.
   7350 // - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0,
   7351 //    allowing a first press to clear selection THEN the second press to leave child window and return to parent.
   7352 // - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently).
   7353 // - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1.
   7354 //   - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags_ClearOnEscape flag dynamically.
   7355 ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count)
   7356 {
   7357     ImGuiContext& g = *GImGui;
   7358     ImGuiWindow* window = g.CurrentWindow;
   7359 
   7360     if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size)
   7361         g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData());
   7362     ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1];
   7363     IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that.
   7364     g.CurrentMultiSelect = ms;
   7365     if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)
   7366         flags |= ImGuiMultiSelectFlags_ScopeWindow;
   7367     if (flags & ImGuiMultiSelectFlags_SingleSelect)
   7368         flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d);
   7369     if (flags & ImGuiMultiSelectFlags_BoxSelect2d)
   7370         flags &= ~ImGuiMultiSelectFlags_BoxSelect1d;
   7371 
   7372     // FIXME: BeginFocusScope()
   7373     const ImGuiID id = window->IDStack.back();
   7374     ms->Clear();
   7375     ms->FocusScopeId = id;
   7376     ms->Flags = flags;
   7377     ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId);
   7378     ms->BackupCursorMaxPos = window->DC.CursorMaxPos;
   7379     ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos;
   7380     PushFocusScope(ms->FocusScopeId);
   7381     if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items.
   7382         window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main;
   7383 
   7384     // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame.
   7385     ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods;
   7386     if (flags & ImGuiMultiSelectFlags_NoRangeSelect)
   7387         ms->KeyMods &= ~ImGuiMod_Shift;
   7388 
   7389     // Bind storage
   7390     ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id);
   7391     storage->ID = id;
   7392     storage->LastFrameActive = g.FrameCount;
   7393     storage->LastSelectionSize = selection_size;
   7394     storage->Window = window;
   7395     ms->Storage = storage;
   7396 
   7397     // Output to user
   7398     ms->IO.Requests.resize(0);
   7399     ms->IO.RangeSrcItem = storage->RangeSrcItem;
   7400     ms->IO.NavIdItem = storage->NavIdItem;
   7401     ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false;
   7402     ms->IO.ItemsCount = items_count;
   7403 
   7404     // Clear when using Navigation to move within the scope
   7405     // (we compare FocusScopeId so it possible to use multiple selections inside a same window)
   7406     bool request_clear = false;
   7407     bool request_select_all = false;
   7408     if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData)
   7409     {
   7410         if (ms->KeyMods & ImGuiMod_Shift)
   7411             ms->IsKeyboardSetRange = true;
   7412         if (ms->IsKeyboardSetRange)
   7413             IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear?
   7414         if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
   7415             request_clear = true;
   7416     }
   7417     else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
   7418     {
   7419         // Also clear on leaving scope (may be optional?)
   7420         if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
   7421             request_clear = true;
   7422     }
   7423 
   7424     // Box-select handling: update active state.
   7425     ImGuiBoxSelectState* bs = &g.BoxSelectState;
   7426     if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
   7427     {
   7428         ms->BoxSelectId = GetID("##BoxSelect");
   7429         if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags))
   7430             request_clear |= bs->RequestClear;
   7431     }
   7432 
   7433     if (ms->IsFocused)
   7434     {
   7435         // Shortcut: Clear selection (Escape)
   7436         // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window.
   7437         // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock.
   7438         if (flags & ImGuiMultiSelectFlags_ClearOnEscape)
   7439         {
   7440             if (selection_size != 0 || bs->IsActive)
   7441                 if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0))
   7442                 {
   7443                     request_clear = true;
   7444                     if (bs->IsActive)
   7445                         BoxSelectDeactivateDrag(bs);
   7446                 }
   7447         }
   7448 
   7449         // Shortcut: Select all (CTRL+A)
   7450         if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
   7451             if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A))
   7452                 request_select_all = true;
   7453     }
   7454 
   7455     if (request_clear || request_select_all)
   7456     {
   7457         MultiSelectAddSetAll(ms, request_select_all);
   7458         if (!request_select_all)
   7459             storage->LastSelectionSize = 0;
   7460     }
   7461     ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1;
   7462     ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid;
   7463 
   7464     if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
   7465         DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO);
   7466 
   7467     return &ms->IO;
   7468 }
   7469 
   7470 // Return updated ImGuiMultiSelectIO structure.
   7471 // Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
   7472 ImGuiMultiSelectIO* ImGui::EndMultiSelect()
   7473 {
   7474     ImGuiContext& g = *GImGui;
   7475     ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
   7476     ImGuiMultiSelectState* storage = ms->Storage;
   7477     ImGuiWindow* window = g.CurrentWindow;
   7478     IM_ASSERT(ms->FocusScopeId == g.CurrentFocusScopeId);
   7479     IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow);
   7480     IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect);
   7481 
   7482     ImRect scope_rect = CalcScopeRect(ms, window);
   7483     if (ms->IsFocused)
   7484     {
   7485         // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here.
   7486         if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure)
   7487         {
   7488             IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId.
   7489             storage->RangeSrcItem = ImGuiSelectionUserData_Invalid;
   7490         }
   7491         if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid)
   7492         {
   7493             IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n");
   7494             storage->NavIdItem = ImGuiSelectionUserData_Invalid;
   7495             storage->NavIdSelected = -1;
   7496         }
   7497 
   7498         if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId))
   7499             EndBoxSelect(scope_rect, ms->Flags);
   7500     }
   7501 
   7502     if (ms->IsEndIO == false)
   7503         ms->IO.Requests.resize(0);
   7504 
   7505     // Clear selection when clicking void?
   7506     // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection!
   7507     // The InnerRect test is necessary for non-child/decorated windows.
   7508     bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos);
   7509     if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect))
   7510         scope_hovered &= scope_rect.Contains(g.IO.MousePos);
   7511     if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0)
   7512     {
   7513         if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
   7514         {
   7515             if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1)
   7516             {
   7517                 BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid);
   7518                 FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);
   7519                 SetHoveredID(ms->BoxSelectId);
   7520                 if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
   7521                     SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select.
   7522             }
   7523         }
   7524 
   7525         if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid)
   7526             if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)
   7527                 MultiSelectAddSetAll(ms, false);
   7528     }
   7529 
   7530     // Courtesy nav wrapping helper flag
   7531     if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX)
   7532     {
   7533         IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope
   7534         ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
   7535     }
   7536 
   7537     // Unwind
   7538     window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos);
   7539     PopFocusScope();
   7540 
   7541     if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
   7542         DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO);
   7543 
   7544     ms->FocusScopeId = 0;
   7545     ms->Flags = ImGuiMultiSelectFlags_None;
   7546     g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL;
   7547 
   7548     return &ms->IO;
   7549 }
   7550 
   7551 void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
   7552 {
   7553     // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
   7554     // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.
   7555     ImGuiContext& g = *GImGui;
   7556     g.NextItemData.SelectionUserData = selection_user_data;
   7557     g.NextItemData.FocusScopeId = g.CurrentFocusScopeId;
   7558 
   7559     if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)
   7560     {
   7561         // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping)
   7562         g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
   7563         if (ms->IO.RangeSrcItem == selection_user_data)
   7564             ms->RangeSrcPassedBy = true;
   7565     }
   7566     else
   7567     {
   7568         g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
   7569     }
   7570 }
   7571 
   7572 // In charge of:
   7573 // - Applying SetAll for submitted items.
   7574 // - Applying SetRange for submitted items and record end points.
   7575 // - Altering button behavior flags to facilitate use with drag and drop.
   7576 void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags)
   7577 {
   7578     ImGuiContext& g = *GImGui;
   7579     ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
   7580 
   7581     bool selected = *p_selected;
   7582     if (ms->IsFocused)
   7583     {
   7584         ImGuiMultiSelectState* storage = ms->Storage;
   7585         ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
   7586         IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
   7587 
   7588         // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect().
   7589         // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper.
   7590         // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect()
   7591         if (ms->LoopRequestSetAll != -1)
   7592             selected = (ms->LoopRequestSetAll == 1);
   7593 
   7594         // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection)
   7595         // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function)
   7596         if (ms->IsKeyboardSetRange)
   7597         {
   7598             IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0);
   7599             const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id;     // Assume that g.NavJustMovedToId is not clipped.
   7600             if (is_range_dst)
   7601                 ms->RangeDstPassedBy = true;
   7602             if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst
   7603             {
   7604                 storage->RangeSrcItem = item_data;
   7605                 storage->RangeSelected = selected ? 1 : 0;
   7606             }
   7607             const bool is_range_src = storage->RangeSrcItem == item_data;
   7608             if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy)
   7609             {
   7610                 // Apply range-select value to visible items
   7611                 IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1);
   7612                 selected = (storage->RangeSelected != 0);
   7613             }
   7614             else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
   7615             {
   7616                 // Clear other items
   7617                 selected = false;
   7618             }
   7619         }
   7620         *p_selected = selected;
   7621     }
   7622 
   7623     // Alter button behavior flags
   7624     // To handle drag and drop of multiple items we need to avoid clearing selection on click.
   7625     // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items.
   7626     if (p_button_flags != NULL)
   7627     {
   7628         ImGuiButtonFlags button_flags = *p_button_flags;
   7629         button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
   7630         if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease))
   7631             button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
   7632         else
   7633             button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
   7634         *p_button_flags = button_flags;
   7635     }
   7636 }
   7637 
   7638 // In charge of:
   7639 // - Auto-select on navigation.
   7640 // - Box-select toggle handling.
   7641 // - Right-click handling.
   7642 // - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse.
   7643 // - Record current selection state for RangeSrc
   7644 // This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite.
   7645 void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
   7646 {
   7647     ImGuiContext& g = *GImGui;
   7648     ImGuiWindow* window = g.CurrentWindow;
   7649 
   7650     bool selected = *p_selected;
   7651     bool pressed = *p_pressed;
   7652     ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
   7653     ImGuiMultiSelectState* storage = ms->Storage;
   7654     if (pressed)
   7655         ms->IsFocused = true;
   7656 
   7657     bool hovered = false;
   7658     if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)
   7659         hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
   7660     if (!ms->IsFocused && !hovered)
   7661         return;
   7662 
   7663     ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
   7664 
   7665     ImGuiMultiSelectFlags flags = ms->Flags;
   7666     const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0;
   7667     bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0;
   7668     bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0;
   7669 
   7670     bool apply_to_range_src = false;
   7671 
   7672     if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
   7673         apply_to_range_src = true;
   7674     if (ms->IsEndIO == false)
   7675     {
   7676         ms->IO.Requests.resize(0);
   7677         ms->IsEndIO = true;
   7678     }
   7679 
   7680     // Auto-select as you navigate a list
   7681     if (g.NavJustMovedToId == id)
   7682     {
   7683         if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
   7684         {
   7685             if (is_ctrl && is_shift)
   7686                 pressed = true;
   7687             else if (!is_ctrl)
   7688                 selected = pressed = true;
   7689         }
   7690         else
   7691         {
   7692             // With NoAutoSelect, using Shift+keyboard performs a write/copy
   7693             if (is_shift)
   7694                 pressed = true;
   7695             else if (!is_ctrl)
   7696                 apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this
   7697         }
   7698     }
   7699 
   7700     if (apply_to_range_src)
   7701     {
   7702         storage->RangeSrcItem = item_data;
   7703         storage->RangeSelected = selected; // Will be updated at the end of this function anyway.
   7704     }
   7705 
   7706     // Box-select toggle handling
   7707     if (ms->BoxSelectId != 0)
   7708         if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))
   7709         {
   7710             const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect);
   7711             const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect);
   7712             if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr))
   7713             {
   7714                 if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce)
   7715                 {
   7716                     pressed = true; // First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway)
   7717                     bs->IsStartedSetNavIdOnce = false;
   7718                 }
   7719                 else
   7720                 {
   7721                     selected = !selected;
   7722                     MultiSelectAddSetRange(ms, selected, +1, item_data, item_data);
   7723                 }
   7724                 storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1);
   7725             }
   7726         }
   7727 
   7728     // Right-click handling.
   7729     // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816
   7730     if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
   7731     {
   7732         if (g.ActiveId != 0 && g.ActiveId != id)
   7733             ClearActiveID();
   7734         SetFocusID(id, window);
   7735         if (!pressed && !selected)
   7736         {
   7737             pressed = true;
   7738             is_ctrl = is_shift = false;
   7739         }
   7740     }
   7741 
   7742     // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected.
   7743     // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something
   7744     // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional?
   7745     const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput);
   7746 
   7747     // Alter selection
   7748     if (pressed && (!enter_pressed || !selected))
   7749     {
   7750         // Box-select
   7751         ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
   7752         if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
   7753             if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)
   7754                 BoxSelectPreStartDrag(ms->BoxSelectId, item_data);
   7755 
   7756         //----------------------------------------------------------------------------------------
   7757         // ACTION                      | Begin  | Pressed/Activated  | End
   7758         //----------------------------------------------------------------------------------------
   7759         // Keys Navigated:             | Clear  | Src=item, Sel=1               SetRange 1
   7760         // Keys Navigated: Ctrl        | n/a    | n/a
   7761         // Keys Navigated:      Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
   7762         // Keys Navigated: Ctrl+Shift  | n/a    | Dst=item, Sel=Src  => Clear + SetRange Src-Dst
   7763         // Keys Activated:             | n/a    | Src=item, Sel=1    => Clear + SetRange 1
   7764         // Keys Activated: Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetSange 1
   7765         // Keys Activated:      Shift  | n/a    | Dst=item, Sel=1    => Clear + SetSange 1
   7766         //----------------------------------------------------------------------------------------
   7767         // Mouse Pressed:              | n/a    | Src=item, Sel=1,   => Clear + SetRange 1
   7768         // Mouse Pressed:  Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetRange 1
   7769         // Mouse Pressed:       Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
   7770         // Mouse Pressed:  Ctrl+Shift  | n/a    | Dst=item, Sel=!Sel =>         SetRange Src-Dst
   7771         //----------------------------------------------------------------------------------------
   7772 
   7773         if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
   7774         {
   7775             bool request_clear = false;
   7776             if (is_singleselect)
   7777                 request_clear = true;
   7778             else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl)
   7779                 request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true;
   7780             else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl)
   7781                 request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again.
   7782             if (request_clear)
   7783                 MultiSelectAddSetAll(ms, false);
   7784         }
   7785 
   7786         int range_direction;
   7787         bool range_selected;
   7788         if (is_shift && !is_singleselect)
   7789         {
   7790             //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue);
   7791             if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
   7792                 storage->RangeSrcItem = item_data;
   7793             if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
   7794             {
   7795                 // Shift+Arrow always select
   7796                 // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected)
   7797                 range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
   7798             }
   7799             else
   7800             {
   7801                 // Shift+Arrow copy source selection state
   7802                 // Shift+Click always copy from target selection state
   7803                 if (ms->IsKeyboardSetRange)
   7804                     range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
   7805                 else
   7806                     range_selected = !selected;
   7807             }
   7808             range_direction = ms->RangeSrcPassedBy ? +1 : -1;
   7809         }
   7810         else
   7811         {
   7812             // Ctrl inverts selection, otherwise always select
   7813             if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
   7814                 selected = is_ctrl ? !selected : true;
   7815             else
   7816                 selected = !selected;
   7817             storage->RangeSrcItem = item_data;
   7818             range_selected = selected;
   7819             range_direction = +1;
   7820         }
   7821         MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data);
   7822     }
   7823 
   7824     // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect)
   7825     if (storage->RangeSrcItem == item_data)
   7826         storage->RangeSelected = selected ? 1 : 0;
   7827 
   7828     // Update/store the selection state of focused item
   7829     if (g.NavId == id)
   7830     {
   7831         storage->NavIdItem = item_data;
   7832         storage->NavIdSelected = selected ? 1 : 0;
   7833     }
   7834     if (storage->NavIdItem == item_data)
   7835         ms->NavIdPassedBy = true;
   7836     ms->LastSubmittedItem = item_data;
   7837 
   7838     *p_selected = selected;
   7839     *p_pressed = pressed;
   7840 }
   7841 
   7842 void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected)
   7843 {
   7844     ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid };
   7845     ms->IO.Requests.resize(0);      // Can always clear previous requests
   7846     ms->IO.Requests.push_back(req); // Add new request
   7847 }
   7848 
   7849 void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item)
   7850 {
   7851     // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges)
   7852     if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0)
   7853     {
   7854         ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1];
   7855         if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected)
   7856         {
   7857             prev->RangeLastItem = last_item;
   7858             return;
   7859         }
   7860     }
   7861 
   7862     ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item };
   7863     ms->IO.Requests.push_back(req); // Add new request
   7864 }
   7865 
   7866 void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage)
   7867 {
   7868 #ifndef IMGUI_DISABLE_DEBUG_TOOLS
   7869     const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.
   7870     if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
   7871     bool open = TreeNode((void*)(intptr_t)storage->ID, "MultiSelect 0x%08X in '%s'%s", storage->ID, storage->Window ? storage->Window->Name : "N/A", is_active ? "" : " *Inactive*");
   7872     if (!is_active) { PopStyleColor(); }
   7873     if (!open)
   7874         return;
   7875     Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected);
   7876     Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected);
   7877     Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user
   7878     TreePop();
   7879 #else
   7880     IM_UNUSED(storage);
   7881 #endif
   7882 }
   7883 
   7884 //-------------------------------------------------------------------------
   7885 // [SECTION] Widgets: Multi-Select helpers
   7886 //-------------------------------------------------------------------------
   7887 // - ImGuiSelectionBasicStorage
   7888 // - ImGuiSelectionExternalStorage
   7889 //-------------------------------------------------------------------------
   7890 
   7891 ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage()
   7892 {
   7893     Size = 0;
   7894     PreserveOrder = false;
   7895     UserData = NULL;
   7896     AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; };
   7897     _SelectionOrder = 1; // Always >0
   7898 }
   7899 
   7900 void ImGuiSelectionBasicStorage::Clear()
   7901 {
   7902     Size = 0;
   7903     _SelectionOrder = 1; // Always >0
   7904     _Storage.Data.resize(0);
   7905 }
   7906 
   7907 void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r)
   7908 {
   7909     ImSwap(Size, r.Size);
   7910     ImSwap(_SelectionOrder, r._SelectionOrder);
   7911     _Storage.Data.swap(r._Storage.Data);
   7912 }
   7913 
   7914 bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const
   7915 {
   7916     return _Storage.GetInt(id, 0) != 0;
   7917 }
   7918 
   7919 static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs)
   7920 {
   7921     int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i;
   7922     int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i;
   7923     return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);
   7924 }
   7925 
   7926 // GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user.
   7927 // (e.g. store unselected vs compact down, compact down on demand, use raw ImVector<ImGuiID> instead of ImGuiStorage...)
   7928 bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id)
   7929 {
   7930     ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it;
   7931     ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size;
   7932     if (PreserveOrder && it == NULL && it_end != NULL)
   7933         ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt()
   7934     if (it == NULL)
   7935         it = _Storage.Data.Data;
   7936     IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);
   7937     if (it != it_end)
   7938         while (it->val_i == 0 && it < it_end)
   7939             it++;
   7940     const bool has_more = (it != it_end);
   7941     *opaque_it = has_more ? (void**)(it + 1) : (void**)(it);
   7942     *out_id = has_more ? it->key : 0;
   7943     if (PreserveOrder && !has_more)
   7944         _Storage.BuildSortByKey();
   7945     return has_more;
   7946 }
   7947 
   7948 void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected)
   7949 {
   7950     int* p_int = _Storage.GetIntRef(id, 0);
   7951     if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; }
   7952     else if (!selected && *p_int != 0) { *p_int = 0; Size--; }
   7953 }
   7954 
   7955 // Optimized for batch edits (with same value of 'selected')
   7956 static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order)
   7957 {
   7958     ImGuiStorage* storage = &selection->_Storage;
   7959     ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id);
   7960     const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id);
   7961     if (selected == (is_contained && it->val_i != 0))
   7962         return;
   7963     if (selected && !is_contained)
   7964         storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish()
   7965     else if (is_contained)
   7966         it->val_i = selected ? selection_order : 0; // Modify in-place.
   7967     selection->Size += selected ? +1 : -1;
   7968 }
   7969 
   7970 static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends)
   7971 {
   7972     ImGuiStorage* storage = &selection->_Storage;
   7973     if (selected && selection->Size != size_before_amends)
   7974         storage->BuildSortByKey(); // When done selecting: sort everything
   7975 }
   7976 
   7977 // Apply requests coming from BeginMultiSelect() and EndMultiSelect().
   7978 // - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.
   7979 // - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
   7980 //   - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection.
   7981 //   - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform
   7982 //     a lookup in order to have some way to iterate/interpolate between two items.
   7983 // - A full-featured application is likely to allow search/filtering which is likely to lead to using indices
   7984 //   and constructing a view index <> object id/ptr data structure anyway.
   7985 // WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC.
   7986 // Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.
   7987 // The most simple implementation (using indices everywhere) would look like:
   7988 //   for (ImGuiSelectionRequest& req : ms_io->Requests)
   7989 //   {
   7990 //      if (req.Type == ImGuiSelectionRequestType_SetAll)    { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } }
   7991 //      if (req.Type == ImGuiSelectionRequestType_SetRange)  { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } }
   7992 //   }
   7993 void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
   7994 {
   7995     // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional.
   7996     // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect().
   7997     // Other scheme may handle SetAll differently.
   7998     IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!");
   7999     IM_ASSERT(AdapterIndexToStorageId != NULL);
   8000 
   8001     // This is optimized/specialized to cope with very large selections (e.g. 100k+ items)
   8002     // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish().
   8003     // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass.
   8004     // - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector<ImGuiID> to reduce bandwidth, but this is a reasonable trade off to reuse code.
   8005     // - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling
   8006     //   left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.)
   8007     // FIXME-OPT: For each block of consecutive SetRange request:
   8008     // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage.
   8009     // - rewrite sorted storage a single time.
   8010     for (ImGuiSelectionRequest& req : ms_io->Requests)
   8011     {
   8012         if (req.Type == ImGuiSelectionRequestType_SetAll)
   8013         {
   8014             Clear();
   8015             if (req.Selected)
   8016             {
   8017                 _Storage.Data.reserve(ms_io->ItemsCount);
   8018                 const int size_before_amends = _Storage.Data.Size;
   8019                 for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++)
   8020                     ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder);
   8021                 ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
   8022             }
   8023         }
   8024         else if (req.Type == ImGuiSelectionRequestType_SetRange)
   8025         {
   8026             const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1;
   8027             //ImGuiContext& g = *GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io->Requests.index_from_ptr(&req), ms_io->Requests.Size, selection_changes, req.Selected);
   8028             if (selection_changes == 1 || (selection_changes < Size / 100))
   8029             {
   8030                 // Multiple sorted insertion + copy likely to be faster.
   8031                 // Technically we could do a single copy with a little more work (sort sequential SetRange requests)
   8032                 for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
   8033                     SetItemSelected(GetStorageIdFromIndex(idx), req.Selected);
   8034             }
   8035             else
   8036             {
   8037                 // Append insertion + single sort likely be faster.
   8038                 // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1
   8039                 const int size_before_amends = _Storage.Data.Size;
   8040                 int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0);
   8041                 for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection)
   8042                     ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order);
   8043                 if (req.Selected)
   8044                     _SelectionOrder += selection_changes;
   8045                 ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
   8046             }
   8047         }
   8048     }
   8049 }
   8050 
   8051 //-------------------------------------------------------------------------
   8052 
   8053 ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage()
   8054 {
   8055     UserData = NULL;
   8056     AdapterSetItemSelected = NULL;
   8057 }
   8058 
   8059 // Apply requests coming from BeginMultiSelect() and EndMultiSelect().
   8060 // We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage
   8061 // This makes no assumption about underlying storage.
   8062 void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
   8063 {
   8064     IM_ASSERT(AdapterSetItemSelected);
   8065     for (ImGuiSelectionRequest& req : ms_io->Requests)
   8066     {
   8067         if (req.Type == ImGuiSelectionRequestType_SetAll)
   8068             for (int idx = 0; idx < ms_io->ItemsCount; idx++)
   8069                 AdapterSetItemSelected(this, idx, req.Selected);
   8070         if (req.Type == ImGuiSelectionRequestType_SetRange)
   8071             for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
   8072                 AdapterSetItemSelected(this, idx, req.Selected);
   8073     }
   8074 }
   8075 
   8076 //-------------------------------------------------------------------------
   8077 // [SECTION] Widgets: ListBox
   8078 //-------------------------------------------------------------------------
   8079 // - BeginListBox()
   8080 // - EndListBox()
   8081 // - ListBox()
   8082 //-------------------------------------------------------------------------
   8083 
   8084 // This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
   8085 // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
   8086 // 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).
   8087 bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
   8088 {
   8089     ImGuiContext& g = *GImGui;
   8090     ImGuiWindow* window = GetCurrentWindow();
   8091     if (window->SkipItems)
   8092         return false;
   8093 
   8094     const ImGuiStyle& style = g.Style;
   8095     const ImGuiID id = GetID(label);
   8096     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   8097 
   8098     // Size default to hold ~7.25 items.
   8099     // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
   8100     ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
   8101     ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
   8102     ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
   8103     ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
   8104     g.NextItemData.ClearFlags();
   8105 
   8106     if (!IsRectVisible(bb.Min, bb.Max))
   8107     {
   8108         ItemSize(bb.GetSize(), style.FramePadding.y);
   8109         ItemAdd(bb, 0, &frame_bb);
   8110         g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
   8111         return false;
   8112     }
   8113 
   8114     // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well.
   8115     BeginGroup();
   8116     if (label_size.x > 0.0f)
   8117     {
   8118         ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
   8119         RenderText(label_pos, label);
   8120         window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
   8121         AlignTextToFramePadding();
   8122     }
   8123 
   8124     BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);
   8125     return true;
   8126 }
   8127 
   8128 void ImGui::EndListBox()
   8129 {
   8130     ImGuiContext& g = *GImGui;
   8131     ImGuiWindow* window = g.CurrentWindow;
   8132     IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
   8133     IM_UNUSED(window);
   8134 
   8135     EndChild();
   8136     EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
   8137 }
   8138 
   8139 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
   8140 {
   8141     const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
   8142     return value_changed;
   8143 }
   8144 
   8145 // This is merely a helper around BeginListBox(), EndListBox().
   8146 // Considering using those directly to submit custom data or store selection differently.
   8147 bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items)
   8148 {
   8149     ImGuiContext& g = *GImGui;
   8150 
   8151     // Calculate size from "height_in_items"
   8152     if (height_in_items < 0)
   8153         height_in_items = ImMin(items_count, 7);
   8154     float height_in_items_f = height_in_items + 0.25f;
   8155     ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
   8156 
   8157     if (!BeginListBox(label, size))
   8158         return false;
   8159 
   8160     // Assume all items have even height (= 1 line of text). If you need items of different height,
   8161     // you can create a custom version of ListBox() in your code without using the clipper.
   8162     bool value_changed = false;
   8163     ImGuiListClipper clipper;
   8164     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.
   8165     clipper.IncludeItemByIndex(*current_item);
   8166     while (clipper.Step())
   8167         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
   8168         {
   8169             const char* item_text = getter(user_data, i);
   8170             if (item_text == NULL)
   8171                 item_text = "*Unknown item*";
   8172 
   8173             PushID(i);
   8174             const bool item_selected = (i == *current_item);
   8175             if (Selectable(item_text, item_selected))
   8176             {
   8177                 *current_item = i;
   8178                 value_changed = true;
   8179             }
   8180             if (item_selected)
   8181                 SetItemDefaultFocus();
   8182             PopID();
   8183         }
   8184     EndListBox();
   8185 
   8186     if (value_changed)
   8187         MarkItemEdited(g.LastItemData.ID);
   8188 
   8189     return value_changed;
   8190 }
   8191 
   8192 //-------------------------------------------------------------------------
   8193 // [SECTION] Widgets: PlotLines, PlotHistogram
   8194 //-------------------------------------------------------------------------
   8195 // - PlotEx() [Internal]
   8196 // - PlotLines()
   8197 // - PlotHistogram()
   8198 //-------------------------------------------------------------------------
   8199 // Plot/Graph widgets are not very good.
   8200 // Consider writing your own, or using a third-party one, see:
   8201 // - ImPlot https://github.com/epezent/implot
   8202 // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
   8203 //-------------------------------------------------------------------------
   8204 
   8205 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, const ImVec2& size_arg)
   8206 {
   8207     ImGuiContext& g = *GImGui;
   8208     ImGuiWindow* window = GetCurrentWindow();
   8209     if (window->SkipItems)
   8210         return -1;
   8211 
   8212     const ImGuiStyle& style = g.Style;
   8213     const ImGuiID id = window->GetID(label);
   8214 
   8215     const ImVec2 label_size = CalcTextSize(label, NULL, true);
   8216     const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
   8217 
   8218     const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
   8219     const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
   8220     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));
   8221     ItemSize(total_bb, style.FramePadding.y);
   8222     if (!ItemAdd(total_bb, 0, &frame_bb))
   8223         return -1;
   8224     const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags);
   8225 
   8226     // Determine scale from values if not specified
   8227     if (scale_min == FLT_MAX || scale_max == FLT_MAX)
   8228     {
   8229         float v_min = FLT_MAX;
   8230         float v_max = -FLT_MAX;
   8231         for (int i = 0; i < values_count; i++)
   8232         {
   8233             const float v = values_getter(data, i);
   8234             if (v != v) // Ignore NaN values
   8235                 continue;
   8236             v_min = ImMin(v_min, v);
   8237             v_max = ImMax(v_max, v);
   8238         }
   8239         if (scale_min == FLT_MAX)
   8240             scale_min = v_min;
   8241         if (scale_max == FLT_MAX)
   8242             scale_max = v_max;
   8243     }
   8244 
   8245     RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
   8246 
   8247     const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
   8248     int idx_hovered = -1;
   8249     if (values_count >= values_count_min)
   8250     {
   8251         int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
   8252         int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
   8253 
   8254         // Tooltip on hover
   8255         if (hovered && inner_bb.Contains(g.IO.MousePos))
   8256         {
   8257             const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
   8258             const int v_idx = (int)(t * item_count);
   8259             IM_ASSERT(v_idx >= 0 && v_idx < values_count);
   8260 
   8261             const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
   8262             const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
   8263             if (plot_type == ImGuiPlotType_Lines)
   8264                 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
   8265             else if (plot_type == ImGuiPlotType_Histogram)
   8266                 SetTooltip("%d: %8.4g", v_idx, v0);
   8267             idx_hovered = v_idx;
   8268         }
   8269 
   8270         const float t_step = 1.0f / (float)res_w;
   8271         const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
   8272 
   8273         float v0 = values_getter(data, (0 + values_offset) % values_count);
   8274         float t0 = 0.0f;
   8275         ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
   8276         float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f);   // Where does the zero line stands
   8277 
   8278         const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
   8279         const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
   8280 
   8281         for (int n = 0; n < res_w; n++)
   8282         {
   8283             const float t1 = t0 + t_step;
   8284             const int v1_idx = (int)(t0 * item_count + 0.5f);
   8285             IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
   8286             const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
   8287             const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
   8288 
   8289             // 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.
   8290             ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
   8291             ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
   8292             if (plot_type == ImGuiPlotType_Lines)
   8293             {
   8294                 window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
   8295             }
   8296             else if (plot_type == ImGuiPlotType_Histogram)
   8297             {
   8298                 if (pos1.x >= pos0.x + 2.0f)
   8299                     pos1.x -= 1.0f;
   8300                 window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
   8301             }
   8302 
   8303             t0 = t1;
   8304             tp0 = tp1;
   8305         }
   8306     }
   8307 
   8308     // Text overlay
   8309     if (overlay_text)
   8310         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));
   8311 
   8312     if (label_size.x > 0.0f)
   8313         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
   8314 
   8315     // Return hovered index or -1 if none are hovered.
   8316     // 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().
   8317     return idx_hovered;
   8318 }
   8319 
   8320 struct ImGuiPlotArrayGetterData
   8321 {
   8322     const float* Values;
   8323     int Stride;
   8324 
   8325     ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
   8326 };
   8327 
   8328 static float Plot_ArrayGetter(void* data, int idx)
   8329 {
   8330     ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
   8331     const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
   8332     return v;
   8333 }
   8334 
   8335 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)
   8336 {
   8337     ImGuiPlotArrayGetterData data(values, stride);
   8338     PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   8339 }
   8340 
   8341 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)
   8342 {
   8343     PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   8344 }
   8345 
   8346 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)
   8347 {
   8348     ImGuiPlotArrayGetterData data(values, stride);
   8349     PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   8350 }
   8351 
   8352 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)
   8353 {
   8354     PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
   8355 }
   8356 
   8357 //-------------------------------------------------------------------------
   8358 // [SECTION] Widgets: Value helpers
   8359 // Those is not very useful, legacy API.
   8360 //-------------------------------------------------------------------------
   8361 // - Value()
   8362 //-------------------------------------------------------------------------
   8363 
   8364 void ImGui::Value(const char* prefix, bool b)
   8365 {
   8366     Text("%s: %s", prefix, (b ? "true" : "false"));
   8367 }
   8368 
   8369 void ImGui::Value(const char* prefix, int v)
   8370 {
   8371     Text("%s: %d", prefix, v);
   8372 }
   8373 
   8374 void ImGui::Value(const char* prefix, unsigned int v)
   8375 {
   8376     Text("%s: %d", prefix, v);
   8377 }
   8378 
   8379 void ImGui::Value(const char* prefix, float v, const char* float_format)
   8380 {
   8381     if (float_format)
   8382     {
   8383         char fmt[64];
   8384         ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
   8385         Text(fmt, prefix, v);
   8386     }
   8387     else
   8388     {
   8389         Text("%s: %.3f", prefix, v);
   8390     }
   8391 }
   8392 
   8393 //-------------------------------------------------------------------------
   8394 // [SECTION] MenuItem, BeginMenu, EndMenu, etc.
   8395 //-------------------------------------------------------------------------
   8396 // - ImGuiMenuColumns [Internal]
   8397 // - BeginMenuBar()
   8398 // - EndMenuBar()
   8399 // - BeginMainMenuBar()
   8400 // - EndMainMenuBar()
   8401 // - BeginMenu()
   8402 // - EndMenu()
   8403 // - MenuItemEx() [Internal]
   8404 // - MenuItem()
   8405 //-------------------------------------------------------------------------
   8406 
   8407 // Helpers for internal use
   8408 void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
   8409 {
   8410     if (window_reappearing)
   8411         memset(Widths, 0, sizeof(Widths));
   8412     Spacing = (ImU16)spacing;
   8413     CalcNextTotalWidth(true);
   8414     memset(Widths, 0, sizeof(Widths));
   8415     TotalWidth = NextTotalWidth;
   8416     NextTotalWidth = 0;
   8417 }
   8418 
   8419 void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
   8420 {
   8421     ImU16 offset = 0;
   8422     bool want_spacing = false;
   8423     for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
   8424     {
   8425         ImU16 width = Widths[i];
   8426         if (want_spacing && width > 0)
   8427             offset += Spacing;
   8428         want_spacing |= (width > 0);
   8429         if (update_offsets)
   8430         {
   8431             if (i == 1) { OffsetLabel = offset; }
   8432             if (i == 2) { OffsetShortcut = offset; }
   8433             if (i == 3) { OffsetMark = offset; }
   8434         }
   8435         offset += width;
   8436     }
   8437     NextTotalWidth = offset;
   8438 }
   8439 
   8440 float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
   8441 {
   8442     Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
   8443     Widths[1] = ImMax(Widths[1], (ImU16)w_label);
   8444     Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
   8445     Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
   8446     CalcNextTotalWidth(false);
   8447     return (float)ImMax(TotalWidth, NextTotalWidth);
   8448 }
   8449 
   8450 // FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
   8451 // Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
   8452 // 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.
   8453 // Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
   8454 bool ImGui::BeginMenuBar()
   8455 {
   8456     ImGuiWindow* window = GetCurrentWindow();
   8457     if (window->SkipItems)
   8458         return false;
   8459     if (!(window->Flags & ImGuiWindowFlags_MenuBar))
   8460         return false;
   8461 
   8462     IM_ASSERT(!window->DC.MenuBarAppending);
   8463     BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
   8464     PushID("##menubar");
   8465 
   8466     // 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.
   8467     // 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.
   8468     ImRect bar_rect = window->MenuBarRect();
   8469     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));
   8470     clip_rect.ClipWith(window->OuterRectClipped);
   8471     PushClipRect(clip_rect.Min, clip_rect.Max, false);
   8472 
   8473     // 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).
   8474     window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
   8475     window->DC.LayoutType = ImGuiLayoutType_Horizontal;
   8476     window->DC.IsSameLine = false;
   8477     window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
   8478     window->DC.MenuBarAppending = true;
   8479     AlignTextToFramePadding();
   8480     return true;
   8481 }
   8482 
   8483 void ImGui::EndMenuBar()
   8484 {
   8485     ImGuiWindow* window = GetCurrentWindow();
   8486     if (window->SkipItems)
   8487         return;
   8488     ImGuiContext& g = *GImGui;
   8489 
   8490     // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
   8491     if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
   8492     {
   8493         // Try to find out if the request is for one of our child menu
   8494         ImGuiWindow* nav_earliest_child = g.NavWindow;
   8495         while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
   8496             nav_earliest_child = nav_earliest_child->ParentWindow;
   8497         if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
   8498         {
   8499             // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
   8500             // 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 bothering)
   8501             const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
   8502             IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary)
   8503             FocusWindow(window);
   8504             SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
   8505             g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
   8506             g.NavDisableMouseHover = g.NavMousePosDirty = true;
   8507             NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat
   8508         }
   8509     }
   8510 
   8511     IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
   8512     IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
   8513     IM_ASSERT(window->DC.MenuBarAppending);
   8514     PopClipRect();
   8515     PopID();
   8516     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.
   8517 
   8518     // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here.
   8519     ImGuiGroupData& group_data = g.GroupStack.back();
   8520     group_data.EmitItem = false;
   8521     ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos;
   8522     window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, window->DC.CursorMaxPos.x - window->Scroll.x); // Convert ideal extents for scrolling layer equivalent.
   8523     EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore
   8524     window->DC.LayoutType = ImGuiLayoutType_Vertical;
   8525     window->DC.IsSameLine = false;
   8526     window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
   8527     window->DC.MenuBarAppending = false;
   8528     window->DC.CursorMaxPos = restore_cursor_max_pos;
   8529 }
   8530 
   8531 // Important: calling order matters!
   8532 // FIXME: Somehow overlapping with docking tech.
   8533 // FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
   8534 bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
   8535 {
   8536     IM_ASSERT(dir != ImGuiDir_None);
   8537 
   8538     ImGuiWindow* bar_window = FindWindowByName(name);
   8539     if (bar_window == NULL || bar_window->BeginCount == 0)
   8540     {
   8541         // Calculate and set window size/position
   8542         ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
   8543         ImRect avail_rect = viewport->GetBuildWorkRect();
   8544         ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
   8545         ImVec2 pos = avail_rect.Min;
   8546         if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
   8547             pos[axis] = avail_rect.Max[axis] - axis_size;
   8548         ImVec2 size = avail_rect.GetSize();
   8549         size[axis] = axis_size;
   8550         SetNextWindowPos(pos);
   8551         SetNextWindowSize(size);
   8552 
   8553         // Report our size into work area (for next frame) using actual window size
   8554         if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
   8555             viewport->BuildWorkOffsetMin[axis] += axis_size;
   8556         else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
   8557             viewport->BuildWorkOffsetMax[axis] -= axis_size;
   8558     }
   8559 
   8560     window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
   8561     PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
   8562     PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
   8563     bool is_open = Begin(name, NULL, window_flags);
   8564     PopStyleVar(2);
   8565 
   8566     return is_open;
   8567 }
   8568 
   8569 bool ImGui::BeginMainMenuBar()
   8570 {
   8571     ImGuiContext& g = *GImGui;
   8572     ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
   8573 
   8574     // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
   8575     // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
   8576     // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
   8577     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
   8578     ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
   8579     float height = GetFrameHeight();
   8580     bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
   8581     g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
   8582 
   8583     if (is_open)
   8584         BeginMenuBar();
   8585     else
   8586         End();
   8587     return is_open;
   8588 }
   8589 
   8590 void ImGui::EndMainMenuBar()
   8591 {
   8592     EndMenuBar();
   8593 
   8594     // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
   8595     // FIXME: With this strategy we won't be able to restore a NULL focus.
   8596     ImGuiContext& g = *GImGui;
   8597     if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
   8598         FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);
   8599 
   8600     End();
   8601 }
   8602 
   8603 static bool IsRootOfOpenMenuSet()
   8604 {
   8605     ImGuiContext& g = *GImGui;
   8606     ImGuiWindow* window = g.CurrentWindow;
   8607     if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
   8608         return false;
   8609 
   8610     // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others
   8611     // (e.g. inside menu bar vs loose menu items) based on parent ID.
   8612     // This would however prevent the use of e.g. PushID() user code submitting menus.
   8613     // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
   8614     // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
   8615     // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
   8616     // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu.
   8617     // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent window nav layer.
   8618     // This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets in same nav layer would still
   8619     // open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it won't feel weird and if they are far apart
   8620     // it likely won't be a problem anyone runs into.
   8621     const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
   8622     if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer)
   8623         return false;
   8624     return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true);
   8625 }
   8626 
   8627 bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
   8628 {
   8629     ImGuiWindow* window = GetCurrentWindow();
   8630     if (window->SkipItems)
   8631         return false;
   8632 
   8633     ImGuiContext& g = *GImGui;
   8634     const ImGuiStyle& style = g.Style;
   8635     const ImGuiID id = window->GetID(label);
   8636     bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
   8637 
   8638     // 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)
   8639     // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation.
   8640     ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
   8641     if (window->Flags & ImGuiWindowFlags_ChildMenu)
   8642         window_flags |= ImGuiWindowFlags_ChildWindow;
   8643 
   8644     // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
   8645     // 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.
   8646     // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
   8647     if (g.MenusIdSubmittedThisFrame.contains(id))
   8648     {
   8649         if (menu_is_open)
   8650             menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
   8651         else
   8652             g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
   8653         return menu_is_open;
   8654     }
   8655 
   8656     // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
   8657     g.MenusIdSubmittedThisFrame.push_back(id);
   8658 
   8659     ImVec2 label_size = CalcTextSize(label, NULL, true);
   8660 
   8661     // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window)
   8662     // This is only done for items for the menu set and not the full parent window.
   8663     const bool menuset_is_open = IsRootOfOpenMenuSet();
   8664     if (menuset_is_open)
   8665         PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
   8666 
   8667     // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
   8668     // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
   8669     // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
   8670     ImVec2 popup_pos, pos = window->DC.CursorPos;
   8671     PushID(label);
   8672     if (!enabled)
   8673         BeginDisabled();
   8674     const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
   8675     bool pressed;
   8676 
   8677     // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
   8678     const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
   8679     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
   8680     {
   8681         // Menu inside an horizontal menu bar
   8682         // Selectable extend their highlight by half ItemSpacing in each direction.
   8683         // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
   8684         popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight);
   8685         window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
   8686         PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
   8687         float w = label_size.x;
   8688         ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
   8689         pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y));
   8690         RenderText(text_pos, label);
   8691         PopStyleVar();
   8692         window->DC.CursorPos.x += IM_TRUNC(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().
   8693     }
   8694     else
   8695     {
   8696         // Menu inside a regular/vertical menu
   8697         // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
   8698         //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
   8699         popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
   8700         float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
   8701         float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
   8702         float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
   8703         float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
   8704         ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
   8705         pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
   8706         RenderText(text_pos, label);
   8707         if (icon_w > 0.0f)
   8708             RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
   8709         RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
   8710     }
   8711     if (!enabled)
   8712         EndDisabled();
   8713 
   8714     const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover;
   8715     if (menuset_is_open)
   8716         PopItemFlag();
   8717 
   8718     bool want_open = false;
   8719     bool want_open_nav_init = false;
   8720     bool want_close = false;
   8721     if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
   8722     {
   8723         // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
   8724         // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
   8725         bool moving_toward_child_menu = false;
   8726         ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below)
   8727         ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL;
   8728         if (g.HoveredWindow == window && child_menu_window != NULL)
   8729         {
   8730             const float ref_unit = g.FontSize; // FIXME-DPI
   8731             const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;
   8732             const ImRect next_window_rect = child_menu_window->Rect();
   8733             ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);
   8734             ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();
   8735             ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR();
   8736             const float pad_farmost_h = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // Add a bit of extra slack.
   8737             ta.x += child_dir * -0.5f;
   8738             tb.x += child_dir * ref_unit;
   8739             tc.x += child_dir * ref_unit;
   8740             tb.y = ta.y + ImMax((tb.y - pad_farmost_h) - ta.y, -ref_unit * 8.0f); // Triangle has maximum height to limit the slope and the bias toward large sub-menus
   8741             tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f);
   8742             moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
   8743             //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
   8744         }
   8745 
   8746         // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not)
   8747         // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon.
   8748         // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.)
   8749         if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavDisableMouseHover && g.ActiveId == 0)
   8750             want_close = true;
   8751 
   8752         // Open
   8753         // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test)
   8754         if (!menu_is_open && pressed) // Click/activate to open
   8755             want_open = true;
   8756         else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
   8757             want_open = true;
   8758         else if (!menu_is_open && hovered && g.HoveredIdTimer >= 0.30f && g.MouseStationaryTimer >= 0.30f) // Hover to open (timer fallback)
   8759             want_open = true;
   8760         if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
   8761         {
   8762             want_open = want_open_nav_init = true;
   8763             NavMoveRequestCancel();
   8764             NavRestoreHighlightAfterMove();
   8765         }
   8766     }
   8767     else
   8768     {
   8769         // Menu bar
   8770         if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
   8771         {
   8772             want_close = true;
   8773             want_open = menu_is_open = false;
   8774         }
   8775         else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
   8776         {
   8777             want_open = true;
   8778         }
   8779         else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
   8780         {
   8781             want_open = true;
   8782             NavMoveRequestCancel();
   8783         }
   8784     }
   8785 
   8786     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.. }'
   8787         want_close = true;
   8788     if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
   8789         ClosePopupToLevel(g.BeginPopupStack.Size, true);
   8790 
   8791     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
   8792     PopID();
   8793 
   8794     if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
   8795     {
   8796         // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame.
   8797         OpenPopup(label);
   8798     }
   8799     else if (want_open)
   8800     {
   8801         menu_is_open = true;
   8802         OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));
   8803     }
   8804 
   8805     if (menu_is_open)
   8806     {
   8807         ImGuiLastItemData last_item_in_parent = g.LastItemData;
   8808         SetNextWindowPos(popup_pos, ImGuiCond_Always);                  // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
   8809         PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
   8810         menu_is_open = BeginPopupEx(id, window_flags);                  // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
   8811         PopStyleVar();
   8812         if (menu_is_open)
   8813         {
   8814             // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:
   8815             // Perform an init request in the case the popup was already open (via a previous mouse hover)
   8816             if (want_open && want_open_nav_init && !g.NavInitRequest)
   8817             {
   8818                 FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);
   8819                 NavInitWindow(g.CurrentWindow, false);
   8820             }
   8821 
   8822             // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
   8823             // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
   8824             g.LastItemData = last_item_in_parent;
   8825             if (g.HoveredWindow == window)
   8826                 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
   8827         }
   8828     }
   8829     else
   8830     {
   8831         g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
   8832     }
   8833 
   8834     return menu_is_open;
   8835 }
   8836 
   8837 bool ImGui::BeginMenu(const char* label, bool enabled)
   8838 {
   8839     return BeginMenuEx(label, NULL, enabled);
   8840 }
   8841 
   8842 void ImGui::EndMenu()
   8843 {
   8844     // Nav: When a left move request our menu failed, close ourselves.
   8845     ImGuiContext& g = *GImGui;
   8846     ImGuiWindow* window = g.CurrentWindow;
   8847     IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup);  // Mismatched BeginMenu()/EndMenu() calls
   8848     ImGuiWindow* parent_window = window->ParentWindow;  // Should always be != NULL is we passed assert.
   8849     if (window->BeginCount == window->BeginCountPreviousFrame)
   8850         if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
   8851             if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)
   8852             {
   8853                 ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);
   8854                 NavMoveRequestCancel();
   8855             }
   8856 
   8857     EndPopup();
   8858 }
   8859 
   8860 bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
   8861 {
   8862     ImGuiWindow* window = GetCurrentWindow();
   8863     if (window->SkipItems)
   8864         return false;
   8865 
   8866     ImGuiContext& g = *GImGui;
   8867     ImGuiStyle& style = g.Style;
   8868     ImVec2 pos = window->DC.CursorPos;
   8869     ImVec2 label_size = CalcTextSize(label, NULL, true);
   8870 
   8871     // See BeginMenuEx() for comments about this.
   8872     const bool menuset_is_open = IsRootOfOpenMenuSet();
   8873     if (menuset_is_open)
   8874         PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
   8875 
   8876     // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
   8877     // 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.
   8878     bool pressed;
   8879     PushID(label);
   8880     if (!enabled)
   8881         BeginDisabled();
   8882 
   8883     // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
   8884     const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover;
   8885     const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
   8886     if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
   8887     {
   8888         // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
   8889         // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
   8890         float w = label_size.x;
   8891         window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
   8892         ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
   8893         PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
   8894         pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f));
   8895         PopStyleVar();
   8896         if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
   8897             RenderText(text_pos, label);
   8898         window->DC.CursorPos.x += IM_TRUNC(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().
   8899     }
   8900     else
   8901     {
   8902         // Menu item inside a vertical menu
   8903         // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
   8904         //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
   8905         float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
   8906         float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
   8907         float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
   8908         float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
   8909         float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
   8910         pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
   8911         if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
   8912         {
   8913             RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
   8914             if (icon_w > 0.0f)
   8915                 RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
   8916             if (shortcut_w > 0.0f)
   8917             {
   8918                 PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
   8919                 RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
   8920                 PopStyleColor();
   8921             }
   8922             if (selected)
   8923                 RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
   8924         }
   8925     }
   8926     IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
   8927     if (!enabled)
   8928         EndDisabled();
   8929     PopID();
   8930     if (menuset_is_open)
   8931         PopItemFlag();
   8932 
   8933     return pressed;
   8934 }
   8935 
   8936 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
   8937 {
   8938     return MenuItemEx(label, NULL, shortcut, selected, enabled);
   8939 }
   8940 
   8941 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
   8942 {
   8943     if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
   8944     {
   8945         if (p_selected)
   8946             *p_selected = !*p_selected;
   8947         return true;
   8948     }
   8949     return false;
   8950 }
   8951 
   8952 //-------------------------------------------------------------------------
   8953 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
   8954 //-------------------------------------------------------------------------
   8955 // - BeginTabBar()
   8956 // - BeginTabBarEx() [Internal]
   8957 // - EndTabBar()
   8958 // - TabBarLayout() [Internal]
   8959 // - TabBarCalcTabID() [Internal]
   8960 // - TabBarCalcMaxTabWidth() [Internal]
   8961 // - TabBarFindTabById() [Internal]
   8962 // - TabBarFindTabByOrder() [Internal]
   8963 // - TabBarGetCurrentTab() [Internal]
   8964 // - TabBarGetTabName() [Internal]
   8965 // - TabBarRemoveTab() [Internal]
   8966 // - TabBarCloseTab() [Internal]
   8967 // - TabBarScrollClamp() [Internal]
   8968 // - TabBarScrollToTab() [Internal]
   8969 // - TabBarQueueFocus() [Internal]
   8970 // - TabBarQueueReorder() [Internal]
   8971 // - TabBarProcessReorderFromMousePos() [Internal]
   8972 // - TabBarProcessReorder() [Internal]
   8973 // - TabBarScrollingButtons() [Internal]
   8974 // - TabBarTabListPopupButton() [Internal]
   8975 //-------------------------------------------------------------------------
   8976 
   8977 struct ImGuiTabBarSection
   8978 {
   8979     int                 TabCount;               // Number of tabs in this section.
   8980     float               Width;                  // Sum of width of tabs in this section (after shrinking down)
   8981     float               Spacing;                // Horizontal spacing at the end of the section.
   8982 
   8983     ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
   8984 };
   8985 
   8986 namespace ImGui
   8987 {
   8988     static void             TabBarLayout(ImGuiTabBar* tab_bar);
   8989     static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
   8990     static float            TabBarCalcMaxTabWidth();
   8991     static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
   8992     static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
   8993     static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
   8994     static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
   8995 }
   8996 
   8997 ImGuiTabBar::ImGuiTabBar()
   8998 {
   8999     memset(this, 0, sizeof(*this));
   9000     CurrFrameVisible = PrevFrameVisible = -1;
   9001     LastTabItemIdx = -1;
   9002 }
   9003 
   9004 static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
   9005 {
   9006     return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
   9007 }
   9008 
   9009 static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
   9010 {
   9011     const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
   9012     const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
   9013     const int a_section = TabItemGetSectionIdx(a);
   9014     const int b_section = TabItemGetSectionIdx(b);
   9015     if (a_section != b_section)
   9016         return a_section - b_section;
   9017     return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
   9018 }
   9019 
   9020 static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
   9021 {
   9022     const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
   9023     const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
   9024     return (int)(a->BeginOrder - b->BeginOrder);
   9025 }
   9026 
   9027 static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
   9028 {
   9029     ImGuiContext& g = *GImGui;
   9030     return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
   9031 }
   9032 
   9033 static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
   9034 {
   9035     ImGuiContext& g = *GImGui;
   9036     if (g.TabBars.Contains(tab_bar))
   9037         return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
   9038     return ImGuiPtrOrIndex(tab_bar);
   9039 }
   9040 
   9041 bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
   9042 {
   9043     ImGuiContext& g = *GImGui;
   9044     ImGuiWindow* window = g.CurrentWindow;
   9045     if (window->SkipItems)
   9046         return false;
   9047 
   9048     ImGuiID id = window->GetID(str_id);
   9049     ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
   9050     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);
   9051     tab_bar->ID = id;
   9052     tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f);
   9053     tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f);
   9054     //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false))
   9055     flags |= ImGuiTabBarFlags_IsFocused;
   9056     return BeginTabBarEx(tab_bar, tab_bar_bb, flags);
   9057 }
   9058 
   9059 bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
   9060 {
   9061     ImGuiContext& g = *GImGui;
   9062     ImGuiWindow* window = g.CurrentWindow;
   9063     if (window->SkipItems)
   9064         return false;
   9065 
   9066     IM_ASSERT(tab_bar->ID != 0);
   9067     if ((flags & ImGuiTabBarFlags_DockNode) == 0)
   9068         PushOverrideID(tab_bar->ID);
   9069 
   9070     // Add to stack
   9071     g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
   9072     g.CurrentTabBar = tab_bar;
   9073 
   9074     // Append with multiple BeginTabBar()/EndTabBar() pairs.
   9075     tab_bar->BackupCursorPos = window->DC.CursorPos;
   9076     if (tab_bar->CurrFrameVisible == g.FrameCount)
   9077     {
   9078         window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
   9079         tab_bar->BeginCount++;
   9080         return true;
   9081     }
   9082 
   9083     // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
   9084     if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
   9085         ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
   9086     tab_bar->TabsAddedNew = false;
   9087 
   9088     // Flags
   9089     if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
   9090         flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
   9091 
   9092     tab_bar->Flags = flags;
   9093     tab_bar->BarRect = tab_bar_bb;
   9094     tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
   9095     tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
   9096     tab_bar->CurrFrameVisible = g.FrameCount;
   9097     tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
   9098     tab_bar->CurrTabsContentsHeight = 0.0f;
   9099     tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
   9100     tab_bar->FramePadding = g.Style.FramePadding;
   9101     tab_bar->TabsActiveCount = 0;
   9102     tab_bar->LastTabItemIdx = -1;
   9103     tab_bar->BeginCount = 1;
   9104 
   9105     // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
   9106     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
   9107 
   9108     // Draw separator
   9109     // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable)
   9110     const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected);
   9111     if (g.Style.TabBarBorderSize > 0.0f)
   9112     {
   9113         const float y = tab_bar->BarRect.Max.y;
   9114         window->DrawList->AddRectFilled(ImVec2(tab_bar->SeparatorMinX, y - g.Style.TabBarBorderSize), ImVec2(tab_bar->SeparatorMaxX, y), col);
   9115     }
   9116     return true;
   9117 }
   9118 
   9119 void    ImGui::EndTabBar()
   9120 {
   9121     ImGuiContext& g = *GImGui;
   9122     ImGuiWindow* window = g.CurrentWindow;
   9123     if (window->SkipItems)
   9124         return;
   9125 
   9126     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   9127     if (tab_bar == NULL)
   9128     {
   9129         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
   9130         return;
   9131     }
   9132 
   9133     // Fallback in case no TabItem have been submitted
   9134     if (tab_bar->WantLayout)
   9135         TabBarLayout(tab_bar);
   9136 
   9137     // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
   9138     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
   9139     if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
   9140     {
   9141         tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
   9142         window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
   9143     }
   9144     else
   9145     {
   9146         window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
   9147     }
   9148     if (tab_bar->BeginCount > 1)
   9149         window->DC.CursorPos = tab_bar->BackupCursorPos;
   9150 
   9151     tab_bar->LastTabItemIdx = -1;
   9152     if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
   9153         PopID();
   9154 
   9155     g.CurrentTabBarStack.pop_back();
   9156     g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
   9157 }
   9158 
   9159 // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
   9160 static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections)
   9161 {
   9162     return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
   9163 }
   9164 
   9165 // This is called only once a frame before by the first call to ItemTab()
   9166 // The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
   9167 static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
   9168 {
   9169     ImGuiContext& g = *GImGui;
   9170     tab_bar->WantLayout = false;
   9171 
   9172     // Garbage collect by compacting list
   9173     // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
   9174     int tab_dst_n = 0;
   9175     bool need_sort_by_section = false;
   9176     ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
   9177     for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
   9178     {
   9179         ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
   9180         if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
   9181         {
   9182             // Remove tab
   9183             if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
   9184             if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
   9185             if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
   9186             continue;
   9187         }
   9188         if (tab_dst_n != tab_src_n)
   9189             tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
   9190 
   9191         tab = &tab_bar->Tabs[tab_dst_n];
   9192         tab->IndexDuringLayout = (ImS16)tab_dst_n;
   9193 
   9194         // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
   9195         int curr_tab_section_n = TabItemGetSectionIdx(tab);
   9196         if (tab_dst_n > 0)
   9197         {
   9198             ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
   9199             int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
   9200             if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
   9201                 need_sort_by_section = true;
   9202             if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
   9203                 need_sort_by_section = true;
   9204         }
   9205 
   9206         sections[curr_tab_section_n].TabCount++;
   9207         tab_dst_n++;
   9208     }
   9209     if (tab_bar->Tabs.Size != tab_dst_n)
   9210         tab_bar->Tabs.resize(tab_dst_n);
   9211 
   9212     if (need_sort_by_section)
   9213         ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
   9214 
   9215     // Calculate spacing between sections
   9216     sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
   9217     sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
   9218 
   9219     // Setup next selected tab
   9220     ImGuiID scroll_to_tab_id = 0;
   9221     if (tab_bar->NextSelectedTabId)
   9222     {
   9223         tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
   9224         tab_bar->NextSelectedTabId = 0;
   9225         scroll_to_tab_id = tab_bar->SelectedTabId;
   9226     }
   9227 
   9228     // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
   9229     if (tab_bar->ReorderRequestTabId != 0)
   9230     {
   9231         if (TabBarProcessReorder(tab_bar))
   9232             if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
   9233                 scroll_to_tab_id = tab_bar->ReorderRequestTabId;
   9234         tab_bar->ReorderRequestTabId = 0;
   9235     }
   9236 
   9237     // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
   9238     const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
   9239     if (tab_list_popup_button)
   9240         if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
   9241             scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
   9242 
   9243     // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
   9244     // (whereas our tabs are stored as: leading, central, trailing)
   9245     int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
   9246     g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
   9247 
   9248     // Compute ideal tabs widths + store them into shrink buffer
   9249     ImGuiTabItem* most_recently_selected_tab = NULL;
   9250     int curr_section_n = -1;
   9251     bool found_selected_tab_id = false;
   9252     for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
   9253     {
   9254         ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
   9255         IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
   9256 
   9257         if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
   9258             most_recently_selected_tab = tab;
   9259         if (tab->ID == tab_bar->SelectedTabId)
   9260             found_selected_tab_id = true;
   9261         if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
   9262             scroll_to_tab_id = tab->ID;
   9263 
   9264         // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
   9265         // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
   9266         // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
   9267         const char* tab_name = TabBarGetTabName(tab_bar, tab);
   9268         const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
   9269         tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;
   9270 
   9271         int section_n = TabItemGetSectionIdx(tab);
   9272         ImGuiTabBarSection* section = &sections[section_n];
   9273         section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
   9274         curr_section_n = section_n;
   9275 
   9276         // Store data so we can build an array sorted by width if we need to shrink tabs down
   9277         IM_MSVC_WARNING_SUPPRESS(6385);
   9278         ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
   9279         shrink_width_item->Index = tab_n;
   9280         shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
   9281         tab->Width = ImMax(tab->ContentWidth, 1.0f);
   9282     }
   9283 
   9284     // Compute total ideal width (used for e.g. auto-resizing a window)
   9285     tab_bar->WidthAllTabsIdeal = 0.0f;
   9286     for (int section_n = 0; section_n < 3; section_n++)
   9287         tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
   9288 
   9289     // Horizontal scrolling buttons
   9290     // (note that TabBarScrollButtons() will alter BarRect.Max.x)
   9291     if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))
   9292         if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
   9293         {
   9294             scroll_to_tab_id = scroll_and_select_tab->ID;
   9295             if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
   9296                 tab_bar->SelectedTabId = scroll_to_tab_id;
   9297         }
   9298 
   9299     // Shrink widths if full tabs don't fit in their allocated space
   9300     float section_0_w = sections[0].Width + sections[0].Spacing;
   9301     float section_1_w = sections[1].Width + sections[1].Spacing;
   9302     float section_2_w = sections[2].Width + sections[2].Spacing;
   9303     bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
   9304     float width_excess;
   9305     if (central_section_is_visible)
   9306         width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
   9307     else
   9308         width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
   9309 
   9310     // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
   9311     if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
   9312     {
   9313         int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
   9314         int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
   9315         ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);
   9316 
   9317         // Apply shrunk values into tabs and sections
   9318         for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
   9319         {
   9320             ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
   9321             float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width);
   9322             if (shrinked_width < 0.0f)
   9323                 continue;
   9324 
   9325             shrinked_width = ImMax(1.0f, shrinked_width);
   9326             int section_n = TabItemGetSectionIdx(tab);
   9327             sections[section_n].Width -= (tab->Width - shrinked_width);
   9328             tab->Width = shrinked_width;
   9329         }
   9330     }
   9331 
   9332     // Layout all active tabs
   9333     int section_tab_index = 0;
   9334     float tab_offset = 0.0f;
   9335     tab_bar->WidthAllTabs = 0.0f;
   9336     for (int section_n = 0; section_n < 3; section_n++)
   9337     {
   9338         ImGuiTabBarSection* section = &sections[section_n];
   9339         if (section_n == 2)
   9340             tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
   9341 
   9342         for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
   9343         {
   9344             ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
   9345             tab->Offset = tab_offset;
   9346             tab->NameOffset = -1;
   9347             tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
   9348         }
   9349         tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
   9350         tab_offset += section->Spacing;
   9351         section_tab_index += section->TabCount;
   9352     }
   9353 
   9354     // Clear name buffers
   9355     tab_bar->TabsNames.Buf.resize(0);
   9356 
   9357     // If we have lost the selected tab, select the next most recently active one
   9358     if (found_selected_tab_id == false)
   9359         tab_bar->SelectedTabId = 0;
   9360     if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
   9361         scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
   9362 
   9363     // Lock in visible tab
   9364     tab_bar->VisibleTabId = tab_bar->SelectedTabId;
   9365     tab_bar->VisibleTabWasSubmitted = false;
   9366 
   9367     // Apply request requests
   9368     if (scroll_to_tab_id != 0)
   9369         TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
   9370     else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
   9371     {
   9372         const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;
   9373         const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;
   9374         if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f)
   9375         {
   9376             const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f;
   9377             tab_bar->ScrollingTargetDistToVisibility = 0.0f;
   9378             tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step);
   9379         }
   9380         SetKeyOwner(wheel_key, tab_bar->ID);
   9381     }
   9382 
   9383     // Update scrolling
   9384     tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
   9385     tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
   9386     if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
   9387     {
   9388         // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
   9389         // Teleport if we are aiming far off the visible line
   9390         tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
   9391         tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
   9392         const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
   9393         tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
   9394     }
   9395     else
   9396     {
   9397         tab_bar->ScrollingSpeed = 0.0f;
   9398     }
   9399     tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
   9400     tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
   9401 
   9402     // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
   9403     ImGuiWindow* window = g.CurrentWindow;
   9404     window->DC.CursorPos = tab_bar->BarRect.Min;
   9405     ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
   9406     window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
   9407 }
   9408 
   9409 // Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack.
   9410 static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
   9411 {
   9412     IM_ASSERT(docked_window == NULL); // master branch only
   9413     IM_UNUSED(docked_window);
   9414     if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
   9415     {
   9416         ImGuiID id = ImHashStr(label);
   9417         KeepAliveID(id);
   9418         return id;
   9419     }
   9420     else
   9421     {
   9422         ImGuiWindow* window = GImGui->CurrentWindow;
   9423         return window->GetID(label);
   9424     }
   9425 }
   9426 
   9427 static float ImGui::TabBarCalcMaxTabWidth()
   9428 {
   9429     ImGuiContext& g = *GImGui;
   9430     return g.FontSize * 20.0f;
   9431 }
   9432 
   9433 ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
   9434 {
   9435     if (tab_id != 0)
   9436         for (int n = 0; n < tab_bar->Tabs.Size; n++)
   9437             if (tab_bar->Tabs[n].ID == tab_id)
   9438                 return &tab_bar->Tabs[n];
   9439     return NULL;
   9440 }
   9441 
   9442 // Order = visible order, not submission order! (which is tab->BeginOrder)
   9443 ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order)
   9444 {
   9445     if (order < 0 || order >= tab_bar->Tabs.Size)
   9446         return NULL;
   9447     return &tab_bar->Tabs[order];
   9448 }
   9449 
   9450 ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar)
   9451 {
   9452     if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size)
   9453         return NULL;
   9454     return &tab_bar->Tabs[tab_bar->LastTabItemIdx];
   9455 }
   9456 
   9457 const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
   9458 {
   9459     if (tab->NameOffset == -1)
   9460         return "N/A";
   9461     IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size);
   9462     return tab_bar->TabsNames.Buf.Data + tab->NameOffset;
   9463 }
   9464 
   9465 // The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
   9466 void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
   9467 {
   9468     if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
   9469         tab_bar->Tabs.erase(tab);
   9470     if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
   9471     if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
   9472     if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
   9473 }
   9474 
   9475 // Called on manual closure attempt
   9476 void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
   9477 {
   9478     if (tab->Flags & ImGuiTabItemFlags_Button)
   9479         return; // A button appended with TabItemButton().
   9480 
   9481     if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0)
   9482     {
   9483         // This will remove a frame of lag for selecting another tab on closure.
   9484         // 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
   9485         tab->WantClose = true;
   9486         if (tab_bar->VisibleTabId == tab->ID)
   9487         {
   9488             tab->LastFrameVisible = -1;
   9489             tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
   9490         }
   9491     }
   9492     else
   9493     {
   9494         // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
   9495         if (tab_bar->VisibleTabId != tab->ID)
   9496             TabBarQueueFocus(tab_bar, tab);
   9497     }
   9498 }
   9499 
   9500 static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
   9501 {
   9502     scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
   9503     return ImMax(scrolling, 0.0f);
   9504 }
   9505 
   9506 // Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
   9507 static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
   9508 {
   9509     ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
   9510     if (tab == NULL)
   9511         return;
   9512     if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
   9513         return;
   9514 
   9515     ImGuiContext& g = *GImGui;
   9516     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)
   9517     int order = TabBarGetTabOrder(tab_bar, tab);
   9518 
   9519     // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
   9520     float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections);
   9521 
   9522     // We make all tabs positions all relative Sections[0].Width to make code simpler
   9523     float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
   9524     float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
   9525     tab_bar->ScrollingTargetDistToVisibility = 0.0f;
   9526     if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
   9527     {
   9528         // Scroll to the left
   9529         tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
   9530         tab_bar->ScrollingTarget = tab_x1;
   9531     }
   9532     else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
   9533     {
   9534         // Scroll to the right
   9535         tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
   9536         tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
   9537     }
   9538 }
   9539 
   9540 void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
   9541 {
   9542     tab_bar->NextSelectedTabId = tab->ID;
   9543 }
   9544 
   9545 void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset)
   9546 {
   9547     IM_ASSERT(offset != 0);
   9548     IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
   9549     tab_bar->ReorderRequestTabId = tab->ID;
   9550     tab_bar->ReorderRequestOffset = (ImS16)offset;
   9551 }
   9552 
   9553 void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos)
   9554 {
   9555     ImGuiContext& g = *GImGui;
   9556     IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
   9557     if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
   9558         return;
   9559 
   9560     const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
   9561     const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
   9562 
   9563     // Count number of contiguous tabs we are crossing over
   9564     const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
   9565     const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
   9566     int dst_idx = src_idx;
   9567     for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
   9568     {
   9569         // Reordered tabs must share the same section
   9570         const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
   9571         if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
   9572             break;
   9573         if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
   9574             break;
   9575         dst_idx = i;
   9576 
   9577         // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
   9578         const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
   9579         const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
   9580         //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
   9581         if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
   9582             break;
   9583     }
   9584 
   9585     if (dst_idx != src_idx)
   9586         TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
   9587 }
   9588 
   9589 bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
   9590 {
   9591     ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
   9592     if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
   9593         return false;
   9594 
   9595     //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
   9596     int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset;
   9597     if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
   9598         return false;
   9599 
   9600     // Reordered tabs must share the same section
   9601     // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
   9602     ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
   9603     if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
   9604         return false;
   9605     if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
   9606         return false;
   9607 
   9608     ImGuiTabItem item_tmp = *tab1;
   9609     ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
   9610     ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
   9611     const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
   9612     memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
   9613     *tab2 = item_tmp;
   9614 
   9615     if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
   9616         MarkIniSettingsDirty();
   9617     return true;
   9618 }
   9619 
   9620 static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
   9621 {
   9622     ImGuiContext& g = *GImGui;
   9623     ImGuiWindow* window = g.CurrentWindow;
   9624 
   9625     const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
   9626     const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
   9627 
   9628     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
   9629     //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));
   9630 
   9631     int select_dir = 0;
   9632     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
   9633     arrow_col.w *= 0.5f;
   9634 
   9635     PushStyleColor(ImGuiCol_Text, arrow_col);
   9636     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
   9637     const float backup_repeat_delay = g.IO.KeyRepeatDelay;
   9638     const float backup_repeat_rate = g.IO.KeyRepeatRate;
   9639     g.IO.KeyRepeatDelay = 0.250f;
   9640     g.IO.KeyRepeatRate = 0.200f;
   9641     float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
   9642     window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
   9643     if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
   9644         select_dir = -1;
   9645     window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
   9646     if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
   9647         select_dir = +1;
   9648     PopStyleColor(2);
   9649     g.IO.KeyRepeatRate = backup_repeat_rate;
   9650     g.IO.KeyRepeatDelay = backup_repeat_delay;
   9651 
   9652     ImGuiTabItem* tab_to_scroll_to = NULL;
   9653     if (select_dir != 0)
   9654         if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
   9655         {
   9656             int selected_order = TabBarGetTabOrder(tab_bar, tab_item);
   9657             int target_order = selected_order + select_dir;
   9658 
   9659             // Skip tab item buttons until another tab item is found or end is reached
   9660             while (tab_to_scroll_to == NULL)
   9661             {
   9662                 // If we are at the end of the list, still scroll to make our tab visible
   9663                 tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
   9664 
   9665                 // Cross through buttons
   9666                 // (even if first/last item is a button, return it so we can update the scroll)
   9667                 if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
   9668                 {
   9669                     target_order += select_dir;
   9670                     selected_order += select_dir;
   9671                     tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
   9672                 }
   9673             }
   9674         }
   9675     window->DC.CursorPos = backup_cursor_pos;
   9676     tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
   9677 
   9678     return tab_to_scroll_to;
   9679 }
   9680 
   9681 static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
   9682 {
   9683     ImGuiContext& g = *GImGui;
   9684     ImGuiWindow* window = g.CurrentWindow;
   9685 
   9686     // We use g.Style.FramePadding.y to match the square ArrowButton size
   9687     const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
   9688     const ImVec2 backup_cursor_pos = window->DC.CursorPos;
   9689     window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
   9690     tab_bar->BarRect.Min.x += tab_list_popup_button_width;
   9691 
   9692     ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
   9693     arrow_col.w *= 0.5f;
   9694     PushStyleColor(ImGuiCol_Text, arrow_col);
   9695     PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
   9696     bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
   9697     PopStyleColor(2);
   9698 
   9699     ImGuiTabItem* tab_to_select = NULL;
   9700     if (open)
   9701     {
   9702         for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
   9703         {
   9704             ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
   9705             if (tab->Flags & ImGuiTabItemFlags_Button)
   9706                 continue;
   9707 
   9708             const char* tab_name = TabBarGetTabName(tab_bar, tab);
   9709             if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
   9710                 tab_to_select = tab;
   9711         }
   9712         EndCombo();
   9713     }
   9714 
   9715     window->DC.CursorPos = backup_cursor_pos;
   9716     return tab_to_select;
   9717 }
   9718 
   9719 //-------------------------------------------------------------------------
   9720 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
   9721 //-------------------------------------------------------------------------
   9722 // - BeginTabItem()
   9723 // - EndTabItem()
   9724 // - TabItemButton()
   9725 // - TabItemEx() [Internal]
   9726 // - SetTabItemClosed()
   9727 // - TabItemCalcSize() [Internal]
   9728 // - TabItemBackground() [Internal]
   9729 // - TabItemLabelAndCloseButton() [Internal]
   9730 //-------------------------------------------------------------------------
   9731 
   9732 bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
   9733 {
   9734     ImGuiContext& g = *GImGui;
   9735     ImGuiWindow* window = g.CurrentWindow;
   9736     if (window->SkipItems)
   9737         return false;
   9738 
   9739     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   9740     if (tab_bar == NULL)
   9741     {
   9742         IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
   9743         return false;
   9744     }
   9745     IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
   9746 
   9747     bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
   9748     if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
   9749     {
   9750         ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
   9751         PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
   9752     }
   9753     return ret;
   9754 }
   9755 
   9756 void    ImGui::EndTabItem()
   9757 {
   9758     ImGuiContext& g = *GImGui;
   9759     ImGuiWindow* window = g.CurrentWindow;
   9760     if (window->SkipItems)
   9761         return;
   9762 
   9763     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   9764     if (tab_bar == NULL)
   9765     {
   9766         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
   9767         return;
   9768     }
   9769     IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
   9770     ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
   9771     if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
   9772         PopID();
   9773 }
   9774 
   9775 bool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
   9776 {
   9777     ImGuiContext& g = *GImGui;
   9778     ImGuiWindow* window = g.CurrentWindow;
   9779     if (window->SkipItems)
   9780         return false;
   9781 
   9782     ImGuiTabBar* tab_bar = g.CurrentTabBar;
   9783     if (tab_bar == NULL)
   9784     {
   9785         IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
   9786         return false;
   9787     }
   9788     return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
   9789 }
   9790 
   9791 bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)
   9792 {
   9793     // Layout whole tab bar if not already done
   9794     ImGuiContext& g = *GImGui;
   9795     if (tab_bar->WantLayout)
   9796     {
   9797         ImGuiNextItemData backup_next_item_data = g.NextItemData;
   9798         TabBarLayout(tab_bar);
   9799         g.NextItemData = backup_next_item_data;
   9800     }
   9801     ImGuiWindow* window = g.CurrentWindow;
   9802     if (window->SkipItems)
   9803         return false;
   9804 
   9805     const ImGuiStyle& style = g.Style;
   9806     const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
   9807 
   9808     // If the user called us with *p_open == false, we early out and don't render.
   9809     // 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.
   9810     IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
   9811     if (p_open && !*p_open)
   9812     {
   9813         ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
   9814         return false;
   9815     }
   9816 
   9817     IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
   9818     IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
   9819 
   9820     // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
   9821     if (flags & ImGuiTabItemFlags_NoCloseButton)
   9822         p_open = NULL;
   9823     else if (p_open == NULL)
   9824         flags |= ImGuiTabItemFlags_NoCloseButton;
   9825 
   9826     // Acquire tab data
   9827     ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
   9828     bool tab_is_new = false;
   9829     if (tab == NULL)
   9830     {
   9831         tab_bar->Tabs.push_back(ImGuiTabItem());
   9832         tab = &tab_bar->Tabs.back();
   9833         tab->ID = id;
   9834         tab_bar->TabsAddedNew = tab_is_new = true;
   9835     }
   9836     tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
   9837 
   9838     // Calculate tab contents size
   9839     ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));
   9840     tab->RequestedWidth = -1.0f;
   9841     if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth)
   9842         size.x = tab->RequestedWidth = g.NextItemData.Width;
   9843     if (tab_is_new)
   9844         tab->Width = ImMax(1.0f, size.x);
   9845     tab->ContentWidth = size.x;
   9846     tab->BeginOrder = tab_bar->TabsActiveCount++;
   9847 
   9848     const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
   9849     const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
   9850     const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
   9851     const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
   9852     const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
   9853     tab->LastFrameVisible = g.FrameCount;
   9854     tab->Flags = flags;
   9855 
   9856     // Append name _WITH_ the zero-terminator
   9857     if (docked_window != NULL)
   9858     {
   9859         IM_ASSERT(docked_window == NULL); // master branch only
   9860     }
   9861     else
   9862     {
   9863         tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
   9864         tab_bar->TabsNames.append(label, label + strlen(label) + 1);
   9865     }
   9866 
   9867     // Update selected tab
   9868     if (!is_tab_button)
   9869     {
   9870         if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
   9871             if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
   9872                 TabBarQueueFocus(tab_bar, tab); // New tabs gets activated
   9873         if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar
   9874             TabBarQueueFocus(tab_bar, tab);
   9875     }
   9876 
   9877     // Lock visibility
   9878     // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
   9879     bool tab_contents_visible = (tab_bar->VisibleTabId == id);
   9880     if (tab_contents_visible)
   9881         tab_bar->VisibleTabWasSubmitted = true;
   9882 
   9883     // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
   9884     if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
   9885         if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
   9886             tab_contents_visible = true;
   9887 
   9888     // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
   9889     // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
   9890     if (tab_appearing && (!tab_bar_appearing || tab_is_new))
   9891     {
   9892         ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
   9893         if (is_tab_button)
   9894             return false;
   9895         return tab_contents_visible;
   9896     }
   9897 
   9898     if (tab_bar->SelectedTabId == id)
   9899         tab->LastFrameSelected = g.FrameCount;
   9900 
   9901     // Backup current layout position
   9902     const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
   9903 
   9904     // Layout
   9905     const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
   9906     size.x = tab->Width;
   9907     if (is_central_section)
   9908         window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
   9909     else
   9910         window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
   9911     ImVec2 pos = window->DC.CursorPos;
   9912     ImRect bb(pos, pos + size);
   9913 
   9914     // 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)
   9915     const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
   9916     if (want_clip_rect)
   9917         PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
   9918 
   9919     ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
   9920     ItemSize(bb.GetSize(), style.FramePadding.y);
   9921     window->DC.CursorMaxPos = backup_cursor_max_pos;
   9922 
   9923     if (!ItemAdd(bb, id))
   9924     {
   9925         if (want_clip_rect)
   9926             PopClipRect();
   9927         window->DC.CursorPos = backup_main_cursor_pos;
   9928         return tab_contents_visible;
   9929     }
   9930 
   9931     // Click to Select a tab
   9932     // Allow the close button to overlap
   9933     ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
   9934     if (g.DragDropActive)
   9935         button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
   9936     bool hovered, held;
   9937     bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
   9938     if (pressed && !is_tab_button)
   9939         TabBarQueueFocus(tab_bar, tab);
   9940 
   9941     // Drag and drop: re-order tabs
   9942     if (held && !tab_appearing && IsMouseDragging(0))
   9943     {
   9944         if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
   9945         {
   9946             // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
   9947             if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
   9948             {
   9949                 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
   9950             }
   9951             else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
   9952             {
   9953                 TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
   9954             }
   9955         }
   9956     }
   9957 
   9958 #if 0
   9959     if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
   9960     {
   9961         // Enlarge tab display when hovering
   9962         bb.Max.x = bb.Min.x + IM_TRUNC(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
   9963         display_draw_list = GetForegroundDrawList(window);
   9964         TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
   9965     }
   9966 #endif
   9967 
   9968     // Render tab shape
   9969     ImDrawList* display_draw_list = window->DrawList;
   9970     const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed));
   9971     TabItemBackground(display_draw_list, bb, flags, tab_col);
   9972     if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f)
   9973     {
   9974         float x_offset = IM_TRUNC(0.4f * style.TabRounding);
   9975         if (x_offset < 2.0f * g.CurrentDpiScale)
   9976             x_offset = 0.0f;
   9977         float y_offset = 1.0f * g.CurrentDpiScale;
   9978         display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize);
   9979     }
   9980     RenderNavHighlight(bb, id);
   9981 
   9982     // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
   9983     const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
   9984     if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button)
   9985         TabBarQueueFocus(tab_bar, tab);
   9986 
   9987     if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
   9988         flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
   9989 
   9990     // Render tab label, process close button
   9991     const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0;
   9992     bool just_closed;
   9993     bool text_clipped;
   9994     TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
   9995     if (just_closed && p_open != NULL)
   9996     {
   9997         *p_open = false;
   9998         TabBarCloseTab(tab_bar, tab);
   9999     }
  10000 
  10001     // Restore main window position so user can draw there
  10002     if (want_clip_rect)
  10003         PopClipRect();
  10004     window->DC.CursorPos = backup_main_cursor_pos;
  10005 
  10006     // Tooltip
  10007     // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
  10008     // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
  10009     // FIXME: This is a mess.
  10010     // FIXME: We may want disabled tab to still display the tooltip?
  10011     if (text_clipped && g.HoveredId == id && !held)
  10012         if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
  10013             SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
  10014 
  10015     IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
  10016     if (is_tab_button)
  10017         return pressed;
  10018     return tab_contents_visible;
  10019 }
  10020 
  10021 // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
  10022 // To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
  10023 // Tabs closed by the close button will automatically be flagged to avoid this issue.
  10024 void    ImGui::SetTabItemClosed(const char* label)
  10025 {
  10026     ImGuiContext& g = *GImGui;
  10027     bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
  10028     if (is_within_manual_tab_bar)
  10029     {
  10030         ImGuiTabBar* tab_bar = g.CurrentTabBar;
  10031         ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
  10032         if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
  10033             tab->WantClose = true; // Will be processed by next call to TabBarLayout()
  10034     }
  10035 }
  10036 
  10037 ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)
  10038 {
  10039     ImGuiContext& g = *GImGui;
  10040     ImVec2 label_size = CalcTextSize(label, NULL, true);
  10041     ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
  10042     if (has_close_button_or_unsaved_marker)
  10043         size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
  10044     else
  10045         size.x += g.Style.FramePadding.x + 1.0f;
  10046     return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
  10047 }
  10048 
  10049 ImVec2 ImGui::TabItemCalcSize(ImGuiWindow*)
  10050 {
  10051     IM_ASSERT(0); // This function exists to facilitate merge with 'docking' branch.
  10052     return ImVec2(0.0f, 0.0f);
  10053 }
  10054 
  10055 void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
  10056 {
  10057     // 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.
  10058     ImGuiContext& g = *GImGui;
  10059     const float width = bb.GetWidth();
  10060     IM_UNUSED(flags);
  10061     IM_ASSERT(width > 0.0f);
  10062     const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
  10063     const float y1 = bb.Min.y + 1.0f;
  10064     const float y2 = bb.Max.y - g.Style.TabBarBorderSize;
  10065     draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
  10066     draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
  10067     draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
  10068     draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
  10069     draw_list->PathFillConvex(col);
  10070     if (g.Style.TabBorderSize > 0.0f)
  10071     {
  10072         draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
  10073         draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
  10074         draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
  10075         draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
  10076         draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
  10077     }
  10078 }
  10079 
  10080 // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
  10081 // We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
  10082 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)
  10083 {
  10084     ImGuiContext& g = *GImGui;
  10085     ImVec2 label_size = CalcTextSize(label, NULL, true);
  10086 
  10087     if (out_just_closed)
  10088         *out_just_closed = false;
  10089     if (out_text_clipped)
  10090         *out_text_clipped = false;
  10091 
  10092     if (bb.GetWidth() <= 1.0f)
  10093         return;
  10094 
  10095     // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
  10096     // But right now if you want to alter text color of tabs this is what you need to do.
  10097 #if 0
  10098     const float backup_alpha = g.Style.Alpha;
  10099     if (!is_contents_visible)
  10100         g.Style.Alpha *= 0.7f;
  10101 #endif
  10102 
  10103     // Render text label (with clipping + alpha gradient) + unsaved marker
  10104     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);
  10105     ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
  10106 
  10107     // Return clipped state ignoring the close button
  10108     if (out_text_clipped)
  10109     {
  10110         *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;
  10111         //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));
  10112     }
  10113 
  10114     const float button_sz = g.FontSize;
  10115     const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y);
  10116 
  10117     // Close Button & Unsaved Marker
  10118     // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
  10119     //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
  10120     //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
  10121     //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
  10122     bool close_button_pressed = false;
  10123     bool close_button_visible = false;
  10124     if (close_button_id != 0)
  10125         if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
  10126             if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
  10127                 close_button_visible = true;
  10128     bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
  10129 
  10130     if (close_button_visible)
  10131     {
  10132         ImGuiLastItemData last_item_backup = g.LastItemData;
  10133         if (CloseButton(close_button_id, button_pos))
  10134             close_button_pressed = true;
  10135         g.LastItemData = last_item_backup;
  10136 
  10137         // Close with middle mouse button
  10138         if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
  10139             close_button_pressed = true;
  10140     }
  10141     else if (unsaved_marker_visible)
  10142     {
  10143         const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));
  10144         RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
  10145     }
  10146 
  10147     // This is all rather complicated
  10148     // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
  10149     // 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..
  10150     float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
  10151     if (close_button_visible || unsaved_marker_visible)
  10152     {
  10153         text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);
  10154         text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;
  10155         ellipsis_max_x = text_pixel_clip_bb.Max.x;
  10156     }
  10157     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);
  10158 
  10159 #if 0
  10160     if (!is_contents_visible)
  10161         g.Style.Alpha = backup_alpha;
  10162 #endif
  10163 
  10164     if (out_just_closed)
  10165         *out_just_closed = close_button_pressed;
  10166 }
  10167 
  10168 
  10169 #endif // #ifndef IMGUI_DISABLE