cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

imgui_widgets.cpp (367873B)


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