19f464c52Smaya// dear imgui, v1.68 WIP
29f464c52Smaya// (widgets code)
39f464c52Smaya
49f464c52Smaya/*
59f464c52Smaya
69f464c52SmayaIndex of this file:
79f464c52Smaya
89f464c52Smaya// [SECTION] Forward Declarations
99f464c52Smaya// [SECTION] Widgets: Text, etc.
109f464c52Smaya// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
119f464c52Smaya// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
129f464c52Smaya// [SECTION] Widgets: ComboBox
139f464c52Smaya// [SECTION] Data Type and Data Formatting Helpers
149f464c52Smaya// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
159f464c52Smaya// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
169f464c52Smaya// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
179f464c52Smaya// [SECTION] Widgets: InputText, InputTextMultiline
189f464c52Smaya// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
199f464c52Smaya// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
209f464c52Smaya// [SECTION] Widgets: Selectable
219f464c52Smaya// [SECTION] Widgets: ListBox
229f464c52Smaya// [SECTION] Widgets: PlotLines, PlotHistogram
239f464c52Smaya// [SECTION] Widgets: Value helpers
249f464c52Smaya// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
259f464c52Smaya// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
269f464c52Smaya// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
279f464c52Smaya
289f464c52Smaya*/
299f464c52Smaya
309f464c52Smaya#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
319f464c52Smaya#define _CRT_SECURE_NO_WARNINGS
329f464c52Smaya#endif
339f464c52Smaya
349f464c52Smaya#include "imgui.h"
359f464c52Smaya#ifndef IMGUI_DEFINE_MATH_OPERATORS
369f464c52Smaya#define IMGUI_DEFINE_MATH_OPERATORS
379f464c52Smaya#endif
389f464c52Smaya#include "imgui_internal.h"
399f464c52Smaya
409f464c52Smaya#include <ctype.h>      // toupper, isprint
419f464c52Smaya#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
429f464c52Smaya#include <stddef.h>     // intptr_t
439f464c52Smaya#else
449f464c52Smaya#include <stdint.h>     // intptr_t
459f464c52Smaya#endif
469f464c52Smaya
479f464c52Smaya// Visual Studio warnings
489f464c52Smaya#ifdef _MSC_VER
499f464c52Smaya#pragma warning (disable: 4127) // condition expression is constant
509f464c52Smaya#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
519f464c52Smaya#endif
529f464c52Smaya
539f464c52Smaya// Clang/GCC warnings with -Weverything
549f464c52Smaya#ifdef __clang__
559f464c52Smaya#pragma clang diagnostic ignored "-Wold-style-cast"         // warning : use of old-style cast                              // yes, they are more terse.
569f464c52Smaya#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.
579f464c52Smaya#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.
589f464c52Smaya#pragma clang diagnostic ignored "-Wsign-conversion"        // warning : implicit conversion changes signedness             //
599f464c52Smaya#if __has_warning("-Wzero-as-null-pointer-constant")
609f464c52Smaya#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning : zero as null pointer constant              // some standard header variations use #define NULL 0
619f464c52Smaya#endif
629f464c52Smaya#if __has_warning("-Wdouble-promotion")
639f464c52Smaya#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.
649f464c52Smaya#endif
659f464c52Smaya#elif defined(__GNUC__)
669f464c52Smaya#pragma GCC diagnostic ignored "-Wformat-nonliteral"        // warning: format not a string literal, format string not checked
679f464c52Smaya#if __GNUC__ >= 8
689f464c52Smaya#pragma GCC diagnostic ignored "-Wclass-memaccess"          // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
699f464c52Smaya#endif
709f464c52Smaya#endif
719f464c52Smaya
729f464c52Smaya//-------------------------------------------------------------------------
739f464c52Smaya// Data
749f464c52Smaya//-------------------------------------------------------------------------
759f464c52Smaya
769f464c52Smaya// Those MIN/MAX values are not define because we need to point to them
779f464c52Smayastatic const ImS32  IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
789f464c52Smayastatic const ImS32  IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
799f464c52Smayastatic const ImU32  IM_U32_MIN = 0;
809f464c52Smayastatic const ImU32  IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
819f464c52Smaya#ifdef LLONG_MIN
829f464c52Smayastatic const ImS64  IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
839f464c52Smayastatic const ImS64  IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
849f464c52Smaya#else
859f464c52Smayastatic const ImS64  IM_S64_MIN = -9223372036854775807LL - 1;
869f464c52Smayastatic const ImS64  IM_S64_MAX = 9223372036854775807LL;
879f464c52Smaya#endif
889f464c52Smayastatic const ImU64  IM_U64_MIN = 0;
899f464c52Smaya#ifdef ULLONG_MAX
909f464c52Smayastatic const ImU64  IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
919f464c52Smaya#else
929f464c52Smayastatic const ImU64  IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
939f464c52Smaya#endif
949f464c52Smaya
959f464c52Smaya//-------------------------------------------------------------------------
969f464c52Smaya// [SECTION] Forward Declarations
979f464c52Smaya//-------------------------------------------------------------------------
989f464c52Smaya
999f464c52Smaya// Data Type helpers
1009f464c52Smayastatic inline int       DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
1019f464c52Smayastatic void             DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
1029f464c52Smayastatic bool             DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
1039f464c52Smaya
1049f464c52Smaya// For InputTextEx()
1059f464c52Smayastatic bool             InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
1069f464c52Smayastatic int              InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
1079f464c52Smayastatic ImVec2           InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
1089f464c52Smaya
1099f464c52Smaya//-------------------------------------------------------------------------
1109f464c52Smaya// [SECTION] Widgets: Text, etc.
1119f464c52Smaya//-------------------------------------------------------------------------
1129f464c52Smaya// - TextUnformatted()
1139f464c52Smaya// - Text()
1149f464c52Smaya// - TextV()
1159f464c52Smaya// - TextColored()
1169f464c52Smaya// - TextColoredV()
1179f464c52Smaya// - TextDisabled()
1189f464c52Smaya// - TextDisabledV()
1199f464c52Smaya// - TextWrapped()
1209f464c52Smaya// - TextWrappedV()
1219f464c52Smaya// - LabelText()
1229f464c52Smaya// - LabelTextV()
1239f464c52Smaya// - BulletText()
1249f464c52Smaya// - BulletTextV()
1259f464c52Smaya//-------------------------------------------------------------------------
1269f464c52Smaya
1279f464c52Smayavoid ImGui::TextUnformatted(const char* text, const char* text_end)
1289f464c52Smaya{
1299f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
1309f464c52Smaya    if (window->SkipItems)
1319f464c52Smaya        return;
1329f464c52Smaya
1339f464c52Smaya    ImGuiContext& g = *GImGui;
1349f464c52Smaya    IM_ASSERT(text != NULL);
1359f464c52Smaya    const char* text_begin = text;
1369f464c52Smaya    if (text_end == NULL)
1379f464c52Smaya        text_end = text + strlen(text); // FIXME-OPT
1389f464c52Smaya
1399f464c52Smaya    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
1409f464c52Smaya    const float wrap_pos_x = window->DC.TextWrapPos;
1419f464c52Smaya    const bool wrap_enabled = wrap_pos_x >= 0.0f;
1429f464c52Smaya    if (text_end - text > 2000 && !wrap_enabled)
1439f464c52Smaya    {
1449f464c52Smaya        // Long text!
1459f464c52Smaya        // Perform manual coarse clipping to optimize for long multi-line text
1469f464c52Smaya        // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
1479f464c52Smaya        // - 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.
1489f464c52Smaya        // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
1499f464c52Smaya        const char* line = text;
1509f464c52Smaya        const float line_height = GetTextLineHeight();
1519f464c52Smaya        const ImRect clip_rect = window->ClipRect;
1529f464c52Smaya        ImVec2 text_size(0,0);
1539f464c52Smaya
1549f464c52Smaya        if (text_pos.y <= clip_rect.Max.y)
1559f464c52Smaya        {
1569f464c52Smaya            ImVec2 pos = text_pos;
1579f464c52Smaya
1589f464c52Smaya            // Lines to skip (can't skip when logging text)
1599f464c52Smaya            if (!g.LogEnabled)
1609f464c52Smaya            {
1619f464c52Smaya                int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
1629f464c52Smaya                if (lines_skippable > 0)
1639f464c52Smaya                {
1649f464c52Smaya                    int lines_skipped = 0;
1659f464c52Smaya                    while (line < text_end && lines_skipped < lines_skippable)
1669f464c52Smaya                    {
1679f464c52Smaya                        const char* line_end = (const char*)memchr(line, '\n', text_end - line);
1689f464c52Smaya                        if (!line_end)
1699f464c52Smaya                            line_end = text_end;
1709f464c52Smaya                        line = line_end + 1;
1719f464c52Smaya                        lines_skipped++;
1729f464c52Smaya                    }
1739f464c52Smaya                    pos.y += lines_skipped * line_height;
1749f464c52Smaya                }
1759f464c52Smaya            }
1769f464c52Smaya
1779f464c52Smaya            // Lines to render
1789f464c52Smaya            if (line < text_end)
1799f464c52Smaya            {
1809f464c52Smaya                ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
1819f464c52Smaya                while (line < text_end)
1829f464c52Smaya                {
1839f464c52Smaya                    if (IsClippedEx(line_rect, 0, false))
1849f464c52Smaya                        break;
1859f464c52Smaya
1869f464c52Smaya                    const char* line_end = (const char*)memchr(line, '\n', text_end - line);
1879f464c52Smaya                    if (!line_end)
1889f464c52Smaya                        line_end = text_end;
1899f464c52Smaya                    const ImVec2 line_size = CalcTextSize(line, line_end, false);
1909f464c52Smaya                    text_size.x = ImMax(text_size.x, line_size.x);
1919f464c52Smaya                    RenderText(pos, line, line_end, false);
1929f464c52Smaya                    line = line_end + 1;
1939f464c52Smaya                    line_rect.Min.y += line_height;
1949f464c52Smaya                    line_rect.Max.y += line_height;
1959f464c52Smaya                    pos.y += line_height;
1969f464c52Smaya                }
1979f464c52Smaya
1989f464c52Smaya                // Count remaining lines
1999f464c52Smaya                int lines_skipped = 0;
2009f464c52Smaya                while (line < text_end)
2019f464c52Smaya                {
2029f464c52Smaya                    const char* line_end = (const char*)memchr(line, '\n', text_end - line);
2039f464c52Smaya                    if (!line_end)
2049f464c52Smaya                        line_end = text_end;
2059f464c52Smaya                    line = line_end + 1;
2069f464c52Smaya                    lines_skipped++;
2079f464c52Smaya                }
2089f464c52Smaya                pos.y += lines_skipped * line_height;
2099f464c52Smaya            }
2109f464c52Smaya
2119f464c52Smaya            text_size.y += (pos - text_pos).y;
2129f464c52Smaya        }
2139f464c52Smaya
2149f464c52Smaya        ImRect bb(text_pos, text_pos + text_size);
2159f464c52Smaya        ItemSize(text_size);
2169f464c52Smaya        ItemAdd(bb, 0);
2179f464c52Smaya    }
2189f464c52Smaya    else
2199f464c52Smaya    {
2209f464c52Smaya        const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
2219f464c52Smaya        const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
2229f464c52Smaya
2239f464c52Smaya        // Account of baseline offset
2249f464c52Smaya        ImRect bb(text_pos, text_pos + text_size);
2259f464c52Smaya        ItemSize(text_size);
2269f464c52Smaya        if (!ItemAdd(bb, 0))
2279f464c52Smaya            return;
2289f464c52Smaya
2299f464c52Smaya        // Render (we don't hide text after ## in this end-user function)
2309f464c52Smaya        RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
2319f464c52Smaya    }
2329f464c52Smaya}
2339f464c52Smaya
2349f464c52Smayavoid ImGui::Text(const char* fmt, ...)
2359f464c52Smaya{
2369f464c52Smaya    va_list args;
2379f464c52Smaya    va_start(args, fmt);
2389f464c52Smaya    TextV(fmt, args);
2399f464c52Smaya    va_end(args);
2409f464c52Smaya}
2419f464c52Smaya
2429f464c52Smayavoid ImGui::TextV(const char* fmt, va_list args)
2439f464c52Smaya{
2449f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
2459f464c52Smaya    if (window->SkipItems)
2469f464c52Smaya        return;
2479f464c52Smaya
2489f464c52Smaya    ImGuiContext& g = *GImGui;
2499f464c52Smaya    const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
2509f464c52Smaya    TextUnformatted(g.TempBuffer, text_end);
2519f464c52Smaya}
2529f464c52Smaya
2539f464c52Smayavoid ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
2549f464c52Smaya{
2559f464c52Smaya    va_list args;
2569f464c52Smaya    va_start(args, fmt);
2579f464c52Smaya    TextColoredV(col, fmt, args);
2589f464c52Smaya    va_end(args);
2599f464c52Smaya}
2609f464c52Smaya
2619f464c52Smayavoid ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
2629f464c52Smaya{
2639f464c52Smaya    PushStyleColor(ImGuiCol_Text, col);
2649f464c52Smaya    TextV(fmt, args);
2659f464c52Smaya    PopStyleColor();
2669f464c52Smaya}
2679f464c52Smaya
2689f464c52Smayavoid ImGui::TextDisabled(const char* fmt, ...)
2699f464c52Smaya{
2709f464c52Smaya    va_list args;
2719f464c52Smaya    va_start(args, fmt);
2729f464c52Smaya    TextDisabledV(fmt, args);
2739f464c52Smaya    va_end(args);
2749f464c52Smaya}
2759f464c52Smaya
2769f464c52Smayavoid ImGui::TextDisabledV(const char* fmt, va_list args)
2779f464c52Smaya{
2789f464c52Smaya    PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
2799f464c52Smaya    TextV(fmt, args);
2809f464c52Smaya    PopStyleColor();
2819f464c52Smaya}
2829f464c52Smaya
2839f464c52Smayavoid ImGui::TextWrapped(const char* fmt, ...)
2849f464c52Smaya{
2859f464c52Smaya    va_list args;
2869f464c52Smaya    va_start(args, fmt);
2879f464c52Smaya    TextWrappedV(fmt, args);
2889f464c52Smaya    va_end(args);
2899f464c52Smaya}
2909f464c52Smaya
2919f464c52Smayavoid ImGui::TextWrappedV(const char* fmt, va_list args)
2929f464c52Smaya{
2939f464c52Smaya    bool need_backup = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
2949f464c52Smaya    if (need_backup)
2959f464c52Smaya        PushTextWrapPos(0.0f);
2969f464c52Smaya    TextV(fmt, args);
2979f464c52Smaya    if (need_backup)
2989f464c52Smaya        PopTextWrapPos();
2999f464c52Smaya}
3009f464c52Smaya
3019f464c52Smayavoid ImGui::LabelText(const char* label, const char* fmt, ...)
3029f464c52Smaya{
3039f464c52Smaya    va_list args;
3049f464c52Smaya    va_start(args, fmt);
3059f464c52Smaya    LabelTextV(label, fmt, args);
3069f464c52Smaya    va_end(args);
3079f464c52Smaya}
3089f464c52Smaya
3099f464c52Smaya// Add a label+text combo aligned to other label+value widgets
3109f464c52Smayavoid ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
3119f464c52Smaya{
3129f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
3139f464c52Smaya    if (window->SkipItems)
3149f464c52Smaya        return;
3159f464c52Smaya
3169f464c52Smaya    ImGuiContext& g = *GImGui;
3179f464c52Smaya    const ImGuiStyle& style = g.Style;
3189f464c52Smaya    const float w = CalcItemWidth();
3199f464c52Smaya
3209f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
3219f464c52Smaya    const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
3229f464c52Smaya    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);
3239f464c52Smaya    ItemSize(total_bb, style.FramePadding.y);
3249f464c52Smaya    if (!ItemAdd(total_bb, 0))
3259f464c52Smaya        return;
3269f464c52Smaya
3279f464c52Smaya    // Render
3289f464c52Smaya    const char* value_text_begin = &g.TempBuffer[0];
3299f464c52Smaya    const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
3309f464c52Smaya    RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
3319f464c52Smaya    if (label_size.x > 0.0f)
3329f464c52Smaya        RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
3339f464c52Smaya}
3349f464c52Smaya
3359f464c52Smayavoid ImGui::BulletText(const char* fmt, ...)
3369f464c52Smaya{
3379f464c52Smaya    va_list args;
3389f464c52Smaya    va_start(args, fmt);
3399f464c52Smaya    BulletTextV(fmt, args);
3409f464c52Smaya    va_end(args);
3419f464c52Smaya}
3429f464c52Smaya
3439f464c52Smaya// Text with a little bullet aligned to the typical tree node.
3449f464c52Smayavoid ImGui::BulletTextV(const char* fmt, va_list args)
3459f464c52Smaya{
3469f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
3479f464c52Smaya    if (window->SkipItems)
3489f464c52Smaya        return;
3499f464c52Smaya
3509f464c52Smaya    ImGuiContext& g = *GImGui;
3519f464c52Smaya    const ImGuiStyle& style = g.Style;
3529f464c52Smaya
3539f464c52Smaya    const char* text_begin = g.TempBuffer;
3549f464c52Smaya    const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
3559f464c52Smaya    const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
3569f464c52Smaya    const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
3579f464c52Smaya    const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
3589f464c52Smaya    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y)));  // Empty text doesn't add padding
3599f464c52Smaya    ItemSize(bb);
3609f464c52Smaya    if (!ItemAdd(bb, 0))
3619f464c52Smaya        return;
3629f464c52Smaya
3639f464c52Smaya    // Render
3649f464c52Smaya    RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
3659f464c52Smaya    RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
3669f464c52Smaya}
3679f464c52Smaya
3689f464c52Smaya//-------------------------------------------------------------------------
3699f464c52Smaya// [SECTION] Widgets: Main
3709f464c52Smaya//-------------------------------------------------------------------------
3719f464c52Smaya// - ButtonBehavior() [Internal]
3729f464c52Smaya// - Button()
3739f464c52Smaya// - SmallButton()
3749f464c52Smaya// - InvisibleButton()
3759f464c52Smaya// - ArrowButton()
3769f464c52Smaya// - CloseButton() [Internal]
3779f464c52Smaya// - CollapseButton() [Internal]
3789f464c52Smaya// - Scrollbar() [Internal]
3799f464c52Smaya// - Image()
3809f464c52Smaya// - ImageButton()
3819f464c52Smaya// - Checkbox()
3829f464c52Smaya// - CheckboxFlags()
3839f464c52Smaya// - RadioButton()
3849f464c52Smaya// - ProgressBar()
3859f464c52Smaya// - Bullet()
3869f464c52Smaya//-------------------------------------------------------------------------
3879f464c52Smaya
3889f464c52Smayabool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
3899f464c52Smaya{
3909f464c52Smaya    ImGuiContext& g = *GImGui;
3919f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
3929f464c52Smaya
3939f464c52Smaya    if (flags & ImGuiButtonFlags_Disabled)
3949f464c52Smaya    {
3959f464c52Smaya        if (out_hovered) *out_hovered = false;
3969f464c52Smaya        if (out_held) *out_held = false;
3979f464c52Smaya        if (g.ActiveId == id) ClearActiveID();
3989f464c52Smaya        return false;
3999f464c52Smaya    }
4009f464c52Smaya
4019f464c52Smaya    // Default behavior requires click+release on same spot
4029f464c52Smaya    if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
4039f464c52Smaya        flags |= ImGuiButtonFlags_PressedOnClickRelease;
4049f464c52Smaya
4059f464c52Smaya    ImGuiWindow* backup_hovered_window = g.HoveredWindow;
4069f464c52Smaya    if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
4079f464c52Smaya        g.HoveredWindow = window;
4089f464c52Smaya
4099f464c52Smaya#ifdef IMGUI_ENABLE_TEST_ENGINE
4109f464c52Smaya    if (id != 0 && window->DC.LastItemId != id)
4119f464c52Smaya        ImGuiTestEngineHook_ItemAdd(&g, bb, id);
4129f464c52Smaya#endif
4139f464c52Smaya
4149f464c52Smaya    bool pressed = false;
4159f464c52Smaya    bool hovered = ItemHoverable(bb, id);
4169f464c52Smaya
4179f464c52Smaya    // Drag source doesn't report as hovered
4189f464c52Smaya    if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
4199f464c52Smaya        hovered = false;
4209f464c52Smaya
4219f464c52Smaya    // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
4229f464c52Smaya    if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
4239f464c52Smaya        if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
4249f464c52Smaya        {
4259f464c52Smaya            hovered = true;
4269f464c52Smaya            SetHoveredID(id);
4279f464c52Smaya            if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
4289f464c52Smaya            {
4299f464c52Smaya                pressed = true;
4309f464c52Smaya                FocusWindow(window);
4319f464c52Smaya            }
4329f464c52Smaya        }
4339f464c52Smaya
4349f464c52Smaya    if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
4359f464c52Smaya        g.HoveredWindow = backup_hovered_window;
4369f464c52Smaya
4379f464c52Smaya    // 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.
4389f464c52Smaya    if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
4399f464c52Smaya        hovered = false;
4409f464c52Smaya
4419f464c52Smaya    // Mouse
4429f464c52Smaya    if (hovered)
4439f464c52Smaya    {
4449f464c52Smaya        if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
4459f464c52Smaya        {
4469f464c52Smaya            //                        | CLICKING        | HOLDING with ImGuiButtonFlags_Repeat
4479f464c52Smaya            // PressedOnClickRelease  |  <on release>*  |  <on repeat> <on repeat> .. (NOT on release)  <-- MOST COMMON! (*) only if both click/release were over bounds
4489f464c52Smaya            // PressedOnClick         |  <on click>     |  <on click> <on repeat> <on repeat> ..
4499f464c52Smaya            // PressedOnRelease       |  <on release>   |  <on repeat> <on repeat> .. (NOT on release)
4509f464c52Smaya            // PressedOnDoubleClick   |  <on dclick>    |  <on dclick> <on repeat> <on repeat> ..
4519f464c52Smaya            // FIXME-NAV: We don't honor those different behaviors.
4529f464c52Smaya            if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
4539f464c52Smaya            {
4549f464c52Smaya                SetActiveID(id, window);
4559f464c52Smaya                if (!(flags & ImGuiButtonFlags_NoNavFocus))
4569f464c52Smaya                    SetFocusID(id, window);
4579f464c52Smaya                FocusWindow(window);
4589f464c52Smaya            }
4599f464c52Smaya            if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
4609f464c52Smaya            {
4619f464c52Smaya                pressed = true;
4629f464c52Smaya                if (flags & ImGuiButtonFlags_NoHoldingActiveID)
4639f464c52Smaya                    ClearActiveID();
4649f464c52Smaya                else
4659f464c52Smaya                    SetActiveID(id, window); // Hold on ID
4669f464c52Smaya                FocusWindow(window);
4679f464c52Smaya            }
4689f464c52Smaya            if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
4699f464c52Smaya            {
4709f464c52Smaya                if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay))  // Repeat mode trumps <on release>
4719f464c52Smaya                    pressed = true;
4729f464c52Smaya                ClearActiveID();
4739f464c52Smaya            }
4749f464c52Smaya
4759f464c52Smaya            // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
4769f464c52Smaya            // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
4779f464c52Smaya            if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
4789f464c52Smaya                pressed = true;
4799f464c52Smaya        }
4809f464c52Smaya
4819f464c52Smaya        if (pressed)
4829f464c52Smaya            g.NavDisableHighlight = true;
4839f464c52Smaya    }
4849f464c52Smaya
4859f464c52Smaya    // Gamepad/Keyboard navigation
4869f464c52Smaya    // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
4879f464c52Smaya    if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
4889f464c52Smaya        hovered = true;
4899f464c52Smaya
4909f464c52Smaya    if (g.NavActivateDownId == id)
4919f464c52Smaya    {
4929f464c52Smaya        bool nav_activated_by_code = (g.NavActivateId == id);
4939f464c52Smaya        bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
4949f464c52Smaya        if (nav_activated_by_code || nav_activated_by_inputs)
4959f464c52Smaya            pressed = true;
4969f464c52Smaya        if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
4979f464c52Smaya        {
4989f464c52Smaya            // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
4999f464c52Smaya            g.NavActivateId = id; // This is so SetActiveId assign a Nav source
5009f464c52Smaya            SetActiveID(id, window);
5019f464c52Smaya            if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus))
5029f464c52Smaya                SetFocusID(id, window);
5039f464c52Smaya            g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
5049f464c52Smaya        }
5059f464c52Smaya    }
5069f464c52Smaya
5079f464c52Smaya    bool held = false;
5089f464c52Smaya    if (g.ActiveId == id)
5099f464c52Smaya    {
5109f464c52Smaya        if (pressed)
5119f464c52Smaya            g.ActiveIdHasBeenPressed = true;
5129f464c52Smaya        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
5139f464c52Smaya        {
5149f464c52Smaya            if (g.ActiveIdIsJustActivated)
5159f464c52Smaya                g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
5169f464c52Smaya            if (g.IO.MouseDown[0])
5179f464c52Smaya            {
5189f464c52Smaya                held = true;
5199f464c52Smaya            }
5209f464c52Smaya            else
5219f464c52Smaya            {
5229f464c52Smaya                if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
5239f464c52Smaya                    if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay))  // Repeat mode trumps <on release>
5249f464c52Smaya                        if (!g.DragDropActive)
5259f464c52Smaya                            pressed = true;
5269f464c52Smaya                ClearActiveID();
5279f464c52Smaya            }
5289f464c52Smaya            if (!(flags & ImGuiButtonFlags_NoNavFocus))
5299f464c52Smaya                g.NavDisableHighlight = true;
5309f464c52Smaya        }
5319f464c52Smaya        else if (g.ActiveIdSource == ImGuiInputSource_Nav)
5329f464c52Smaya        {
5339f464c52Smaya            if (g.NavActivateDownId != id)
5349f464c52Smaya                ClearActiveID();
5359f464c52Smaya        }
5369f464c52Smaya    }
5379f464c52Smaya
5389f464c52Smaya    if (out_hovered) *out_hovered = hovered;
5399f464c52Smaya    if (out_held) *out_held = held;
5409f464c52Smaya
5419f464c52Smaya    return pressed;
5429f464c52Smaya}
5439f464c52Smaya
5449f464c52Smayabool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
5459f464c52Smaya{
5469f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
5479f464c52Smaya    if (window->SkipItems)
5489f464c52Smaya        return false;
5499f464c52Smaya
5509f464c52Smaya    ImGuiContext& g = *GImGui;
5519f464c52Smaya    const ImGuiStyle& style = g.Style;
5529f464c52Smaya    const ImGuiID id = window->GetID(label);
5539f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
5549f464c52Smaya
5559f464c52Smaya    ImVec2 pos = window->DC.CursorPos;
5569f464c52Smaya    if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // 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)
5579f464c52Smaya        pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
5589f464c52Smaya    ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
5599f464c52Smaya
5609f464c52Smaya    const ImRect bb(pos, pos + size);
5619f464c52Smaya    ItemSize(size, style.FramePadding.y);
5629f464c52Smaya    if (!ItemAdd(bb, id))
5639f464c52Smaya        return false;
5649f464c52Smaya
5659f464c52Smaya    if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
5669f464c52Smaya        flags |= ImGuiButtonFlags_Repeat;
5679f464c52Smaya    bool hovered, held;
5689f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
5699f464c52Smaya    if (pressed)
5709f464c52Smaya        MarkItemEdited(id);
5719f464c52Smaya
5729f464c52Smaya    // Render
5739f464c52Smaya    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
5749f464c52Smaya    RenderNavHighlight(bb, id);
5759f464c52Smaya    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
5769f464c52Smaya    RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
5779f464c52Smaya
5789f464c52Smaya    // Automatically close popups
5799f464c52Smaya    //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
5809f464c52Smaya    //    CloseCurrentPopup();
5819f464c52Smaya
5829f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
5839f464c52Smaya    return pressed;
5849f464c52Smaya}
5859f464c52Smaya
5869f464c52Smayabool ImGui::Button(const char* label, const ImVec2& size_arg)
5879f464c52Smaya{
5889f464c52Smaya    return ButtonEx(label, size_arg, 0);
5899f464c52Smaya}
5909f464c52Smaya
5919f464c52Smaya// Small buttons fits within text without additional vertical spacing.
5929f464c52Smayabool ImGui::SmallButton(const char* label)
5939f464c52Smaya{
5949f464c52Smaya    ImGuiContext& g = *GImGui;
5959f464c52Smaya    float backup_padding_y = g.Style.FramePadding.y;
5969f464c52Smaya    g.Style.FramePadding.y = 0.0f;
5979f464c52Smaya    bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
5989f464c52Smaya    g.Style.FramePadding.y = backup_padding_y;
5999f464c52Smaya    return pressed;
6009f464c52Smaya}
6019f464c52Smaya
6029f464c52Smaya// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
6039f464c52Smaya// 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)
6049f464c52Smayabool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
6059f464c52Smaya{
6069f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
6079f464c52Smaya    if (window->SkipItems)
6089f464c52Smaya        return false;
6099f464c52Smaya
6109f464c52Smaya    // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
6119f464c52Smaya    IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
6129f464c52Smaya
6139f464c52Smaya    const ImGuiID id = window->GetID(str_id);
6149f464c52Smaya    ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
6159f464c52Smaya    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
6169f464c52Smaya    ItemSize(size);
6179f464c52Smaya    if (!ItemAdd(bb, id))
6189f464c52Smaya        return false;
6199f464c52Smaya
6209f464c52Smaya    bool hovered, held;
6219f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
6229f464c52Smaya
6239f464c52Smaya    return pressed;
6249f464c52Smaya}
6259f464c52Smaya
6269f464c52Smayabool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
6279f464c52Smaya{
6289f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
6299f464c52Smaya    if (window->SkipItems)
6309f464c52Smaya        return false;
6319f464c52Smaya
6329f464c52Smaya    ImGuiContext& g = *GImGui;
6339f464c52Smaya    const ImGuiID id = window->GetID(str_id);
6349f464c52Smaya    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
6359f464c52Smaya    const float default_size = GetFrameHeight();
6369f464c52Smaya    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
6379f464c52Smaya    if (!ItemAdd(bb, id))
6389f464c52Smaya        return false;
6399f464c52Smaya
6409f464c52Smaya    if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
6419f464c52Smaya        flags |= ImGuiButtonFlags_Repeat;
6429f464c52Smaya
6439f464c52Smaya    bool hovered, held;
6449f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
6459f464c52Smaya
6469f464c52Smaya    // Render
6479f464c52Smaya    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
6489f464c52Smaya    RenderNavHighlight(bb, id);
6499f464c52Smaya    RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
6509f464c52Smaya    RenderArrow(bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), dir);
6519f464c52Smaya
6529f464c52Smaya    return pressed;
6539f464c52Smaya}
6549f464c52Smaya
6559f464c52Smayabool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
6569f464c52Smaya{
6579f464c52Smaya    float sz = GetFrameHeight();
6589f464c52Smaya    return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
6599f464c52Smaya}
6609f464c52Smaya
6619f464c52Smaya// Button to close a window
6629f464c52Smayabool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
6639f464c52Smaya{
6649f464c52Smaya    ImGuiContext& g = *GImGui;
6659f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
6669f464c52Smaya
6679f464c52Smaya    // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
6689f464c52Smaya    // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
6699f464c52Smaya    const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
6709f464c52Smaya    bool is_clipped = !ItemAdd(bb, id);
6719f464c52Smaya
6729f464c52Smaya    bool hovered, held;
6739f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
6749f464c52Smaya    if (is_clipped)
6759f464c52Smaya        return pressed;
6769f464c52Smaya
6779f464c52Smaya    // Render
6789f464c52Smaya    ImVec2 center = bb.GetCenter();
6799f464c52Smaya    if (hovered)
6809f464c52Smaya        window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
6819f464c52Smaya
6829f464c52Smaya    float cross_extent = (radius * 0.7071f) - 1.0f;
6839f464c52Smaya    ImU32 cross_col = GetColorU32(ImGuiCol_Text);
6849f464c52Smaya    center -= ImVec2(0.5f, 0.5f);
6859f464c52Smaya    window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
6869f464c52Smaya    window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
6879f464c52Smaya
6889f464c52Smaya    return pressed;
6899f464c52Smaya}
6909f464c52Smaya
6919f464c52Smayabool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
6929f464c52Smaya{
6939f464c52Smaya    ImGuiContext& g = *GImGui;
6949f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
6959f464c52Smaya
6969f464c52Smaya    ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
6979f464c52Smaya    ItemAdd(bb, id);
6989f464c52Smaya    bool hovered, held;
6999f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
7009f464c52Smaya
7019f464c52Smaya    ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
7029f464c52Smaya    if (hovered || held)
7039f464c52Smaya        window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9);
7049f464c52Smaya    RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
7059f464c52Smaya
7069f464c52Smaya    // Switch to moving the window after mouse is moved beyond the initial drag threshold
7079f464c52Smaya    if (IsItemActive() && IsMouseDragging())
7089f464c52Smaya        StartMouseMovingWindow(window);
7099f464c52Smaya
7109f464c52Smaya    return pressed;
7119f464c52Smaya}
7129f464c52Smaya
7139f464c52SmayaImGuiID ImGui::GetScrollbarID(ImGuiLayoutType direction)
7149f464c52Smaya{
7159f464c52Smaya    ImGuiContext& g = *GImGui;
7169f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
7179f464c52Smaya    return window->GetID((direction == ImGuiLayoutType_Horizontal) ? "#SCROLLX" : "#SCROLLY");
7189f464c52Smaya}
7199f464c52Smaya
7209f464c52Smaya// Vertical/Horizontal scrollbar
7219f464c52Smaya// The entire piece of code below is rather confusing because:
7229f464c52Smaya// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
7239f464c52Smaya// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
7249f464c52Smaya// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
7259f464c52Smayavoid ImGui::Scrollbar(ImGuiLayoutType direction)
7269f464c52Smaya{
7279f464c52Smaya    ImGuiContext& g = *GImGui;
7289f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
7299f464c52Smaya
7309f464c52Smaya    const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
7319f464c52Smaya    const ImGuiStyle& style = g.Style;
7329f464c52Smaya    const ImGuiID id = GetScrollbarID(direction);
7339f464c52Smaya
7349f464c52Smaya    // Render background
7359f464c52Smaya    bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
7369f464c52Smaya    float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
7379f464c52Smaya    const ImRect window_rect = window->Rect();
7389f464c52Smaya    const float border_size = window->WindowBorderSize;
7399f464c52Smaya    ImRect bb = horizontal
7409f464c52Smaya        ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size)
7419f464c52Smaya        : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size);
7429f464c52Smaya    if (!horizontal)
7439f464c52Smaya        bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
7449f464c52Smaya
7459f464c52Smaya    const float bb_height = bb.GetHeight();
7469f464c52Smaya    if (bb.GetWidth() <= 0.0f || bb_height <= 0.0f)
7479f464c52Smaya        return;
7489f464c52Smaya
7499f464c52Smaya    // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the resize grab)
7509f464c52Smaya    float alpha = 1.0f;
7519f464c52Smaya    if ((direction == ImGuiLayoutType_Vertical) && bb_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
7529f464c52Smaya    {
7539f464c52Smaya        alpha = ImSaturate((bb_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
7549f464c52Smaya        if (alpha <= 0.0f)
7559f464c52Smaya            return;
7569f464c52Smaya    }
7579f464c52Smaya    const bool allow_interaction = (alpha >= 1.0f);
7589f464c52Smaya
7599f464c52Smaya    int window_rounding_corners;
7609f464c52Smaya    if (horizontal)
7619f464c52Smaya        window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
7629f464c52Smaya    else
7639f464c52Smaya        window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
7649f464c52Smaya    window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
7659f464c52Smaya    bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f)));
7669f464c52Smaya
7679f464c52Smaya    // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
7689f464c52Smaya    float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
7699f464c52Smaya    float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
7709f464c52Smaya    float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
7719f464c52Smaya    float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
7729f464c52Smaya
7739f464c52Smaya    // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
7749f464c52Smaya    // But we maintain a minimum size in pixel to allow for the user to still aim inside.
7759f464c52Smaya    IM_ASSERT(ImMax(win_size_contents_v, win_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.
7769f464c52Smaya    const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
7779f464c52Smaya    const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
7789f464c52Smaya    const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
7799f464c52Smaya
7809f464c52Smaya    // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
7819f464c52Smaya    bool held = false;
7829f464c52Smaya    bool hovered = false;
7839f464c52Smaya    const bool previously_held = (g.ActiveId == id);
7849f464c52Smaya    ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
7859f464c52Smaya
7869f464c52Smaya    float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
7879f464c52Smaya    float scroll_ratio = ImSaturate(scroll_v / scroll_max);
7889f464c52Smaya    float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
7899f464c52Smaya    if (held && allow_interaction && grab_h_norm < 1.0f)
7909f464c52Smaya    {
7919f464c52Smaya        float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
7929f464c52Smaya        float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
7939f464c52Smaya        float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
7949f464c52Smaya
7959f464c52Smaya        // Click position in scrollbar normalized space (0.0f->1.0f)
7969f464c52Smaya        const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
7979f464c52Smaya        SetHoveredID(id);
7989f464c52Smaya
7999f464c52Smaya        bool seek_absolute = false;
8009f464c52Smaya        if (!previously_held)
8019f464c52Smaya        {
8029f464c52Smaya            // On initial click calculate the distance between mouse and the center of the grab
8039f464c52Smaya            if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
8049f464c52Smaya            {
8059f464c52Smaya                *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
8069f464c52Smaya            }
8079f464c52Smaya            else
8089f464c52Smaya            {
8099f464c52Smaya                seek_absolute = true;
8109f464c52Smaya                *click_delta_to_grab_center_v = 0.0f;
8119f464c52Smaya            }
8129f464c52Smaya        }
8139f464c52Smaya
8149f464c52Smaya        // Apply scroll
8159f464c52Smaya        // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position
8169f464c52Smaya        const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm));
8179f464c52Smaya        scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
8189f464c52Smaya        if (horizontal)
8199f464c52Smaya            window->Scroll.x = scroll_v;
8209f464c52Smaya        else
8219f464c52Smaya            window->Scroll.y = scroll_v;
8229f464c52Smaya
8239f464c52Smaya        // Update values for rendering
8249f464c52Smaya        scroll_ratio = ImSaturate(scroll_v / scroll_max);
8259f464c52Smaya        grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
8269f464c52Smaya
8279f464c52Smaya        // Update distance to grab now that we have seeked and saturated
8289f464c52Smaya        if (seek_absolute)
8299f464c52Smaya            *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
8309f464c52Smaya    }
8319f464c52Smaya
8329f464c52Smaya    // Render grab
8339f464c52Smaya    const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
8349f464c52Smaya    ImRect grab_rect;
8359f464c52Smaya    if (horizontal)
8369f464c52Smaya        grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y);
8379f464c52Smaya    else
8389f464c52Smaya        grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y));
8399f464c52Smaya    window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
8409f464c52Smaya}
8419f464c52Smaya
8429f464c52Smayavoid ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
8439f464c52Smaya{
8449f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
8459f464c52Smaya    if (window->SkipItems)
8469f464c52Smaya        return;
8479f464c52Smaya
8489f464c52Smaya    ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
8499f464c52Smaya    if (border_col.w > 0.0f)
8509f464c52Smaya        bb.Max += ImVec2(2, 2);
8519f464c52Smaya    ItemSize(bb);
8529f464c52Smaya    if (!ItemAdd(bb, 0))
8539f464c52Smaya        return;
8549f464c52Smaya
8559f464c52Smaya    if (border_col.w > 0.0f)
8569f464c52Smaya    {
8579f464c52Smaya        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
8589f464c52Smaya        window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
8599f464c52Smaya    }
8609f464c52Smaya    else
8619f464c52Smaya    {
8629f464c52Smaya        window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
8639f464c52Smaya    }
8649f464c52Smaya}
8659f464c52Smaya
8669f464c52Smaya// frame_padding < 0: uses FramePadding from style (default)
8679f464c52Smaya// frame_padding = 0: no framing
8689f464c52Smaya// frame_padding > 0: set framing size
8699f464c52Smaya// The color used are the button colors.
8709f464c52Smayabool 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)
8719f464c52Smaya{
8729f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
8739f464c52Smaya    if (window->SkipItems)
8749f464c52Smaya        return false;
8759f464c52Smaya
8769f464c52Smaya    ImGuiContext& g = *GImGui;
8779f464c52Smaya    const ImGuiStyle& style = g.Style;
8789f464c52Smaya
8799f464c52Smaya    // Default to using texture ID as ID. User can still push string/integer prefixes.
8809f464c52Smaya    // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
8819f464c52Smaya    PushID((void*)(intptr_t)user_texture_id);
8829f464c52Smaya    const ImGuiID id = window->GetID("#image");
8839f464c52Smaya    PopID();
8849f464c52Smaya
8859f464c52Smaya    const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
8869f464c52Smaya    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
8879f464c52Smaya    const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
8889f464c52Smaya    ItemSize(bb);
8899f464c52Smaya    if (!ItemAdd(bb, id))
8909f464c52Smaya        return false;
8919f464c52Smaya
8929f464c52Smaya    bool hovered, held;
8939f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
8949f464c52Smaya
8959f464c52Smaya    // Render
8969f464c52Smaya    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
8979f464c52Smaya    RenderNavHighlight(bb, id);
8989f464c52Smaya    RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
8999f464c52Smaya    if (bg_col.w > 0.0f)
9009f464c52Smaya        window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
9019f464c52Smaya    window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
9029f464c52Smaya
9039f464c52Smaya    return pressed;
9049f464c52Smaya}
9059f464c52Smaya
9069f464c52Smayabool ImGui::Checkbox(const char* label, bool* v)
9079f464c52Smaya{
9089f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
9099f464c52Smaya    if (window->SkipItems)
9109f464c52Smaya        return false;
9119f464c52Smaya
9129f464c52Smaya    ImGuiContext& g = *GImGui;
9139f464c52Smaya    const ImGuiStyle& style = g.Style;
9149f464c52Smaya    const ImGuiID id = window->GetID(label);
9159f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
9169f464c52Smaya
9179f464c52Smaya    const float square_sz = GetFrameHeight();
9189f464c52Smaya    const ImVec2 pos = window->DC.CursorPos;
9199f464c52Smaya    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));
9209f464c52Smaya    ItemSize(total_bb, style.FramePadding.y);
9219f464c52Smaya    if (!ItemAdd(total_bb, id))
9229f464c52Smaya        return false;
9239f464c52Smaya
9249f464c52Smaya    bool hovered, held;
9259f464c52Smaya    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
9269f464c52Smaya    if (pressed)
9279f464c52Smaya    {
9289f464c52Smaya        *v = !(*v);
9299f464c52Smaya        MarkItemEdited(id);
9309f464c52Smaya    }
9319f464c52Smaya
9329f464c52Smaya    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
9339f464c52Smaya    RenderNavHighlight(total_bb, id);
9349f464c52Smaya    RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
9359f464c52Smaya    if (*v)
9369f464c52Smaya    {
9379f464c52Smaya        const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));
9389f464c52Smaya        RenderCheckMark(check_bb.Min + ImVec2(pad, pad), GetColorU32(ImGuiCol_CheckMark), square_sz - pad*2.0f);
9399f464c52Smaya    }
9409f464c52Smaya
9419f464c52Smaya    if (g.LogEnabled)
9429f464c52Smaya        LogRenderedText(&total_bb.Min, *v ? "[x]" : "[ ]");
9439f464c52Smaya    if (label_size.x > 0.0f)
9449f464c52Smaya        RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);
9459f464c52Smaya
9469f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
9479f464c52Smaya    return pressed;
9489f464c52Smaya}
9499f464c52Smaya
9509f464c52Smayabool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
9519f464c52Smaya{
9529f464c52Smaya    bool v = ((*flags & flags_value) == flags_value);
9539f464c52Smaya    bool pressed = Checkbox(label, &v);
9549f464c52Smaya    if (pressed)
9559f464c52Smaya    {
9569f464c52Smaya        if (v)
9579f464c52Smaya            *flags |= flags_value;
9589f464c52Smaya        else
9599f464c52Smaya            *flags &= ~flags_value;
9609f464c52Smaya    }
9619f464c52Smaya
9629f464c52Smaya    return pressed;
9639f464c52Smaya}
9649f464c52Smaya
9659f464c52Smayabool ImGui::RadioButton(const char* label, bool active)
9669f464c52Smaya{
9679f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
9689f464c52Smaya    if (window->SkipItems)
9699f464c52Smaya        return false;
9709f464c52Smaya
9719f464c52Smaya    ImGuiContext& g = *GImGui;
9729f464c52Smaya    const ImGuiStyle& style = g.Style;
9739f464c52Smaya    const ImGuiID id = window->GetID(label);
9749f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
9759f464c52Smaya
9769f464c52Smaya    const float square_sz = GetFrameHeight();
9779f464c52Smaya    const ImVec2 pos = window->DC.CursorPos;
9789f464c52Smaya    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
9799f464c52Smaya    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));
9809f464c52Smaya    ItemSize(total_bb, style.FramePadding.y);
9819f464c52Smaya    if (!ItemAdd(total_bb, id))
9829f464c52Smaya        return false;
9839f464c52Smaya
9849f464c52Smaya    ImVec2 center = check_bb.GetCenter();
9859f464c52Smaya    center.x = (float)(int)center.x + 0.5f;
9869f464c52Smaya    center.y = (float)(int)center.y + 0.5f;
9879f464c52Smaya    const float radius = (square_sz - 1.0f) * 0.5f;
9889f464c52Smaya
9899f464c52Smaya    bool hovered, held;
9909f464c52Smaya    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
9919f464c52Smaya    if (pressed)
9929f464c52Smaya        MarkItemEdited(id);
9939f464c52Smaya
9949f464c52Smaya    RenderNavHighlight(total_bb, id);
9959f464c52Smaya    window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
9969f464c52Smaya    if (active)
9979f464c52Smaya    {
9989f464c52Smaya        const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));
9999f464c52Smaya        window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
10009f464c52Smaya    }
10019f464c52Smaya
10029f464c52Smaya    if (style.FrameBorderSize > 0.0f)
10039f464c52Smaya    {
10049f464c52Smaya        window->DrawList->AddCircle(center + ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
10059f464c52Smaya        window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
10069f464c52Smaya    }
10079f464c52Smaya
10089f464c52Smaya    if (g.LogEnabled)
10099f464c52Smaya        LogRenderedText(&total_bb.Min, active ? "(x)" : "( )");
10109f464c52Smaya    if (label_size.x > 0.0f)
10119f464c52Smaya        RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);
10129f464c52Smaya
10139f464c52Smaya    return pressed;
10149f464c52Smaya}
10159f464c52Smaya
10169f464c52Smayabool ImGui::RadioButton(const char* label, int* v, int v_button)
10179f464c52Smaya{
10189f464c52Smaya    const bool pressed = RadioButton(label, *v == v_button);
10199f464c52Smaya    if (pressed)
10209f464c52Smaya        *v = v_button;
10219f464c52Smaya    return pressed;
10229f464c52Smaya}
10239f464c52Smaya
10249f464c52Smaya// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
10259f464c52Smayavoid ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
10269f464c52Smaya{
10279f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
10289f464c52Smaya    if (window->SkipItems)
10299f464c52Smaya        return;
10309f464c52Smaya
10319f464c52Smaya    ImGuiContext& g = *GImGui;
10329f464c52Smaya    const ImGuiStyle& style = g.Style;
10339f464c52Smaya
10349f464c52Smaya    ImVec2 pos = window->DC.CursorPos;
10359f464c52Smaya    ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
10369f464c52Smaya    ItemSize(bb, style.FramePadding.y);
10379f464c52Smaya    if (!ItemAdd(bb, 0))
10389f464c52Smaya        return;
10399f464c52Smaya
10409f464c52Smaya    // Render
10419f464c52Smaya    fraction = ImSaturate(fraction);
10429f464c52Smaya    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
10439f464c52Smaya    bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
10449f464c52Smaya    const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
10459f464c52Smaya    RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
10469f464c52Smaya
10479f464c52Smaya    // Default displaying the fraction as percentage string, but user can override it
10489f464c52Smaya    char overlay_buf[32];
10499f464c52Smaya    if (!overlay)
10509f464c52Smaya    {
10519f464c52Smaya        ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
10529f464c52Smaya        overlay = overlay_buf;
10539f464c52Smaya    }
10549f464c52Smaya
10559f464c52Smaya    ImVec2 overlay_size = CalcTextSize(overlay, NULL);
10569f464c52Smaya    if (overlay_size.x > 0.0f)
10579f464c52Smaya        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);
10589f464c52Smaya}
10599f464c52Smaya
10609f464c52Smayavoid ImGui::Bullet()
10619f464c52Smaya{
10629f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
10639f464c52Smaya    if (window->SkipItems)
10649f464c52Smaya        return;
10659f464c52Smaya
10669f464c52Smaya    ImGuiContext& g = *GImGui;
10679f464c52Smaya    const ImGuiStyle& style = g.Style;
10689f464c52Smaya    const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
10699f464c52Smaya    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
10709f464c52Smaya    ItemSize(bb);
10719f464c52Smaya    if (!ItemAdd(bb, 0))
10729f464c52Smaya    {
10739f464c52Smaya        SameLine(0, style.FramePadding.x*2);
10749f464c52Smaya        return;
10759f464c52Smaya    }
10769f464c52Smaya
10779f464c52Smaya    // Render and stay on same line
10789f464c52Smaya    RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
10799f464c52Smaya    SameLine(0, style.FramePadding.x*2);
10809f464c52Smaya}
10819f464c52Smaya
10829f464c52Smaya//-------------------------------------------------------------------------
10839f464c52Smaya// [SECTION] Widgets: Low-level Layout helpers
10849f464c52Smaya//-------------------------------------------------------------------------
10859f464c52Smaya// - Spacing()
10869f464c52Smaya// - Dummy()
10879f464c52Smaya// - NewLine()
10889f464c52Smaya// - AlignTextToFramePadding()
10899f464c52Smaya// - Separator()
10909f464c52Smaya// - VerticalSeparator() [Internal]
10919f464c52Smaya// - SplitterBehavior() [Internal]
10929f464c52Smaya//-------------------------------------------------------------------------
10939f464c52Smaya
10949f464c52Smayavoid ImGui::Spacing()
10959f464c52Smaya{
10969f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
10979f464c52Smaya    if (window->SkipItems)
10989f464c52Smaya        return;
10999f464c52Smaya    ItemSize(ImVec2(0,0));
11009f464c52Smaya}
11019f464c52Smaya
11029f464c52Smayavoid ImGui::Dummy(const ImVec2& size)
11039f464c52Smaya{
11049f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
11059f464c52Smaya    if (window->SkipItems)
11069f464c52Smaya        return;
11079f464c52Smaya
11089f464c52Smaya    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
11099f464c52Smaya    ItemSize(bb);
11109f464c52Smaya    ItemAdd(bb, 0);
11119f464c52Smaya}
11129f464c52Smaya
11139f464c52Smayavoid ImGui::NewLine()
11149f464c52Smaya{
11159f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
11169f464c52Smaya    if (window->SkipItems)
11179f464c52Smaya        return;
11189f464c52Smaya
11199f464c52Smaya    ImGuiContext& g = *GImGui;
11209f464c52Smaya    const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
11219f464c52Smaya    window->DC.LayoutType = ImGuiLayoutType_Vertical;
11229f464c52Smaya    if (window->DC.CurrentLineSize.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.
11239f464c52Smaya        ItemSize(ImVec2(0,0));
11249f464c52Smaya    else
11259f464c52Smaya        ItemSize(ImVec2(0.0f, g.FontSize));
11269f464c52Smaya    window->DC.LayoutType = backup_layout_type;
11279f464c52Smaya}
11289f464c52Smaya
11299f464c52Smayavoid ImGui::AlignTextToFramePadding()
11309f464c52Smaya{
11319f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
11329f464c52Smaya    if (window->SkipItems)
11339f464c52Smaya        return;
11349f464c52Smaya
11359f464c52Smaya    ImGuiContext& g = *GImGui;
11369f464c52Smaya    window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
11379f464c52Smaya    window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
11389f464c52Smaya}
11399f464c52Smaya
11409f464c52Smaya// Horizontal/vertical separating line
11419f464c52Smayavoid ImGui::Separator()
11429f464c52Smaya{
11439f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
11449f464c52Smaya    if (window->SkipItems)
11459f464c52Smaya        return;
11469f464c52Smaya    ImGuiContext& g = *GImGui;
11479f464c52Smaya
11489f464c52Smaya    // Those flags should eventually be overridable by the user
11499f464c52Smaya    ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
11509f464c52Smaya    IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))));   // Check that only 1 option is selected
11519f464c52Smaya    if (flags & ImGuiSeparatorFlags_Vertical)
11529f464c52Smaya    {
11539f464c52Smaya        VerticalSeparator();
11549f464c52Smaya        return;
11559f464c52Smaya    }
11569f464c52Smaya
11579f464c52Smaya    // Horizontal Separator
11589f464c52Smaya    if (window->DC.ColumnsSet)
11599f464c52Smaya        PopClipRect();
11609f464c52Smaya
11619f464c52Smaya    float x1 = window->Pos.x;
11629f464c52Smaya    float x2 = window->Pos.x + window->Size.x;
11639f464c52Smaya    if (!window->DC.GroupStack.empty())
11649f464c52Smaya        x1 += window->DC.Indent.x;
11659f464c52Smaya
11669f464c52Smaya    const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
11679f464c52Smaya    ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
11689f464c52Smaya    if (!ItemAdd(bb, 0))
11699f464c52Smaya    {
11709f464c52Smaya        if (window->DC.ColumnsSet)
11719f464c52Smaya            PushColumnClipRect();
11729f464c52Smaya        return;
11739f464c52Smaya    }
11749f464c52Smaya
11759f464c52Smaya    window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
11769f464c52Smaya
11779f464c52Smaya    if (g.LogEnabled)
11789f464c52Smaya        LogRenderedText(&bb.Min, "--------------------------------");
11799f464c52Smaya
11809f464c52Smaya    if (window->DC.ColumnsSet)
11819f464c52Smaya    {
11829f464c52Smaya        PushColumnClipRect();
11839f464c52Smaya        window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
11849f464c52Smaya    }
11859f464c52Smaya}
11869f464c52Smaya
11879f464c52Smayavoid ImGui::VerticalSeparator()
11889f464c52Smaya{
11899f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
11909f464c52Smaya    if (window->SkipItems)
11919f464c52Smaya        return;
11929f464c52Smaya    ImGuiContext& g = *GImGui;
11939f464c52Smaya
11949f464c52Smaya    float y1 = window->DC.CursorPos.y;
11959f464c52Smaya    float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y;
11969f464c52Smaya    const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
11979f464c52Smaya    ItemSize(ImVec2(bb.GetWidth(), 0.0f));
11989f464c52Smaya    if (!ItemAdd(bb, 0))
11999f464c52Smaya        return;
12009f464c52Smaya
12019f464c52Smaya    window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
12029f464c52Smaya    if (g.LogEnabled)
12039f464c52Smaya        LogText(" |");
12049f464c52Smaya}
12059f464c52Smaya
12069f464c52Smaya// 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.
12079f464c52Smayabool 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)
12089f464c52Smaya{
12099f464c52Smaya    ImGuiContext& g = *GImGui;
12109f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
12119f464c52Smaya
12129f464c52Smaya    const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
12139f464c52Smaya    window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
12149f464c52Smaya    bool item_add = ItemAdd(bb, id);
12159f464c52Smaya    window->DC.ItemFlags = item_flags_backup;
12169f464c52Smaya    if (!item_add)
12179f464c52Smaya        return false;
12189f464c52Smaya
12199f464c52Smaya    bool hovered, held;
12209f464c52Smaya    ImRect bb_interact = bb;
12219f464c52Smaya    bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
12229f464c52Smaya    ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
12239f464c52Smaya    if (g.ActiveId != id)
12249f464c52Smaya        SetItemAllowOverlap();
12259f464c52Smaya
12269f464c52Smaya    if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
12279f464c52Smaya        SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
12289f464c52Smaya
12299f464c52Smaya    ImRect bb_render = bb;
12309f464c52Smaya    if (held)
12319f464c52Smaya    {
12329f464c52Smaya        ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
12339f464c52Smaya        float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
12349f464c52Smaya
12359f464c52Smaya        // Minimum pane size
12369f464c52Smaya        float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
12379f464c52Smaya        float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
12389f464c52Smaya        if (mouse_delta < -size_1_maximum_delta)
12399f464c52Smaya            mouse_delta = -size_1_maximum_delta;
12409f464c52Smaya        if (mouse_delta > size_2_maximum_delta)
12419f464c52Smaya            mouse_delta = size_2_maximum_delta;
12429f464c52Smaya
12439f464c52Smaya        // Apply resize
12449f464c52Smaya        if (mouse_delta != 0.0f)
12459f464c52Smaya        {
12469f464c52Smaya            if (mouse_delta < 0.0f)
12479f464c52Smaya                IM_ASSERT(*size1 + mouse_delta >= min_size1);
12489f464c52Smaya            if (mouse_delta > 0.0f)
12499f464c52Smaya                IM_ASSERT(*size2 - mouse_delta >= min_size2);
12509f464c52Smaya            *size1 += mouse_delta;
12519f464c52Smaya            *size2 -= mouse_delta;
12529f464c52Smaya            bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
12539f464c52Smaya            MarkItemEdited(id);
12549f464c52Smaya        }
12559f464c52Smaya    }
12569f464c52Smaya
12579f464c52Smaya    // Render
12589f464c52Smaya    const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
12599f464c52Smaya    window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
12609f464c52Smaya
12619f464c52Smaya    return held;
12629f464c52Smaya}
12639f464c52Smaya
12649f464c52Smaya//-------------------------------------------------------------------------
12659f464c52Smaya// [SECTION] Widgets: ComboBox
12669f464c52Smaya//-------------------------------------------------------------------------
12679f464c52Smaya// - BeginCombo()
12689f464c52Smaya// - EndCombo()
12699f464c52Smaya// - Combo()
12709f464c52Smaya//-------------------------------------------------------------------------
12719f464c52Smaya
12729f464c52Smayastatic float CalcMaxPopupHeightFromItemCount(int items_count)
12739f464c52Smaya{
12749f464c52Smaya    ImGuiContext& g = *GImGui;
12759f464c52Smaya    if (items_count <= 0)
12769f464c52Smaya        return FLT_MAX;
12779f464c52Smaya    return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
12789f464c52Smaya}
12799f464c52Smaya
12809f464c52Smayabool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
12819f464c52Smaya{
12829f464c52Smaya    // Always consume the SetNextWindowSizeConstraint() call in our early return paths
12839f464c52Smaya    ImGuiContext& g = *GImGui;
12849f464c52Smaya    ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
12859f464c52Smaya    g.NextWindowData.SizeConstraintCond = 0;
12869f464c52Smaya
12879f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
12889f464c52Smaya    if (window->SkipItems)
12899f464c52Smaya        return false;
12909f464c52Smaya
12919f464c52Smaya    IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
12929f464c52Smaya
12939f464c52Smaya    const ImGuiStyle& style = g.Style;
12949f464c52Smaya    const ImGuiID id = window->GetID(label);
12959f464c52Smaya
12969f464c52Smaya    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
12979f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
12989f464c52Smaya    const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
12999f464c52Smaya    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
13009f464c52Smaya    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));
13019f464c52Smaya    ItemSize(total_bb, style.FramePadding.y);
13029f464c52Smaya    if (!ItemAdd(total_bb, id, &frame_bb))
13039f464c52Smaya        return false;
13049f464c52Smaya
13059f464c52Smaya    bool hovered, held;
13069f464c52Smaya    bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
13079f464c52Smaya    bool popup_open = IsPopupOpen(id);
13089f464c52Smaya
13099f464c52Smaya    const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
13109f464c52Smaya    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
13119f464c52Smaya    RenderNavHighlight(frame_bb, id);
13129f464c52Smaya    if (!(flags & ImGuiComboFlags_NoPreview))
13139f464c52Smaya        window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
13149f464c52Smaya    if (!(flags & ImGuiComboFlags_NoArrowButton))
13159f464c52Smaya    {
13169f464c52Smaya        window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
13179f464c52Smaya        RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
13189f464c52Smaya    }
13199f464c52Smaya    RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
13209f464c52Smaya    if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
13219f464c52Smaya        RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
13229f464c52Smaya    if (label_size.x > 0)
13239f464c52Smaya        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
13249f464c52Smaya
13259f464c52Smaya    if ((pressed || g.NavActivateId == id) && !popup_open)
13269f464c52Smaya    {
13279f464c52Smaya        if (window->DC.NavLayerCurrent == 0)
13289f464c52Smaya            window->NavLastIds[0] = id;
13299f464c52Smaya        OpenPopupEx(id);
13309f464c52Smaya        popup_open = true;
13319f464c52Smaya    }
13329f464c52Smaya
13339f464c52Smaya    if (!popup_open)
13349f464c52Smaya        return false;
13359f464c52Smaya
13369f464c52Smaya    if (backup_next_window_size_constraint)
13379f464c52Smaya    {
13389f464c52Smaya        g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
13399f464c52Smaya        g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
13409f464c52Smaya    }
13419f464c52Smaya    else
13429f464c52Smaya    {
13439f464c52Smaya        if ((flags & ImGuiComboFlags_HeightMask_) == 0)
13449f464c52Smaya            flags |= ImGuiComboFlags_HeightRegular;
13459f464c52Smaya        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_));    // Only one
13469f464c52Smaya        int popup_max_height_in_items = -1;
13479f464c52Smaya        if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
13489f464c52Smaya        else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
13499f464c52Smaya        else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
13509f464c52Smaya        SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
13519f464c52Smaya    }
13529f464c52Smaya
13539f464c52Smaya    char name[16];
13549f464c52Smaya    ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
13559f464c52Smaya
13569f464c52Smaya    // Peak into expected window size so we can position it
13579f464c52Smaya    if (ImGuiWindow* popup_window = FindWindowByName(name))
13589f464c52Smaya        if (popup_window->WasActive)
13599f464c52Smaya        {
13609f464c52Smaya            ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
13619f464c52Smaya            if (flags & ImGuiComboFlags_PopupAlignLeft)
13629f464c52Smaya                popup_window->AutoPosLastDirection = ImGuiDir_Left;
13639f464c52Smaya            ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
13649f464c52Smaya            ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
13659f464c52Smaya            SetNextWindowPos(pos);
13669f464c52Smaya        }
13679f464c52Smaya
13689f464c52Smaya    // Horizontally align ourselves with the framed text
13699f464c52Smaya    ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
13709f464c52Smaya    PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
13719f464c52Smaya    bool ret = Begin(name, NULL, window_flags);
13729f464c52Smaya    PopStyleVar();
13739f464c52Smaya    if (!ret)
13749f464c52Smaya    {
13759f464c52Smaya        EndPopup();
13769f464c52Smaya        IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
13779f464c52Smaya        return false;
13789f464c52Smaya    }
13799f464c52Smaya    return true;
13809f464c52Smaya}
13819f464c52Smaya
13829f464c52Smayavoid ImGui::EndCombo()
13839f464c52Smaya{
13849f464c52Smaya    EndPopup();
13859f464c52Smaya}
13869f464c52Smaya
13879f464c52Smaya// Getter for the old Combo() API: const char*[]
13889f464c52Smayastatic bool Items_ArrayGetter(void* data, int idx, const char** out_text)
13899f464c52Smaya{
13909f464c52Smaya    const char* const* items = (const char* const*)data;
13919f464c52Smaya    if (out_text)
13929f464c52Smaya        *out_text = items[idx];
13939f464c52Smaya    return true;
13949f464c52Smaya}
13959f464c52Smaya
13969f464c52Smaya// Getter for the old Combo() API: "item1\0item2\0item3\0"
13979f464c52Smayastatic bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
13989f464c52Smaya{
13999f464c52Smaya    // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
14009f464c52Smaya    const char* items_separated_by_zeros = (const char*)data;
14019f464c52Smaya    int items_count = 0;
14029f464c52Smaya    const char* p = items_separated_by_zeros;
14039f464c52Smaya    while (*p)
14049f464c52Smaya    {
14059f464c52Smaya        if (idx == items_count)
14069f464c52Smaya            break;
14079f464c52Smaya        p += strlen(p) + 1;
14089f464c52Smaya        items_count++;
14099f464c52Smaya    }
14109f464c52Smaya    if (!*p)
14119f464c52Smaya        return false;
14129f464c52Smaya    if (out_text)
14139f464c52Smaya        *out_text = p;
14149f464c52Smaya    return true;
14159f464c52Smaya}
14169f464c52Smaya
14179f464c52Smaya// Old API, prefer using BeginCombo() nowadays if you can.
14189f464c52Smayabool 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)
14199f464c52Smaya{
14209f464c52Smaya    ImGuiContext& g = *GImGui;
14219f464c52Smaya
14229f464c52Smaya    // Call the getter to obtain the preview string which is a parameter to BeginCombo()
14239f464c52Smaya    const char* preview_value = NULL;
14249f464c52Smaya    if (*current_item >= 0 && *current_item < items_count)
14259f464c52Smaya        items_getter(data, *current_item, &preview_value);
14269f464c52Smaya
14279f464c52Smaya    // 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.
14289f464c52Smaya    if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
14299f464c52Smaya        SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
14309f464c52Smaya
14319f464c52Smaya    if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
14329f464c52Smaya        return false;
14339f464c52Smaya
14349f464c52Smaya    // Display items
14359f464c52Smaya    // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
14369f464c52Smaya    bool value_changed = false;
14379f464c52Smaya    for (int i = 0; i < items_count; i++)
14389f464c52Smaya    {
14399f464c52Smaya        PushID((void*)(intptr_t)i);
14409f464c52Smaya        const bool item_selected = (i == *current_item);
14419f464c52Smaya        const char* item_text;
14429f464c52Smaya        if (!items_getter(data, i, &item_text))
14439f464c52Smaya            item_text = "*Unknown item*";
14449f464c52Smaya        if (Selectable(item_text, item_selected))
14459f464c52Smaya        {
14469f464c52Smaya            value_changed = true;
14479f464c52Smaya            *current_item = i;
14489f464c52Smaya        }
14499f464c52Smaya        if (item_selected)
14509f464c52Smaya            SetItemDefaultFocus();
14519f464c52Smaya        PopID();
14529f464c52Smaya    }
14539f464c52Smaya
14549f464c52Smaya    EndCombo();
14559f464c52Smaya    return value_changed;
14569f464c52Smaya}
14579f464c52Smaya
14589f464c52Smaya// Combo box helper allowing to pass an array of strings.
14599f464c52Smayabool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
14609f464c52Smaya{
14619f464c52Smaya    const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
14629f464c52Smaya    return value_changed;
14639f464c52Smaya}
14649f464c52Smaya
14659f464c52Smaya// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
14669f464c52Smayabool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
14679f464c52Smaya{
14689f464c52Smaya    int items_count = 0;
14699f464c52Smaya    const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
14709f464c52Smaya    while (*p)
14719f464c52Smaya    {
14729f464c52Smaya        p += strlen(p) + 1;
14739f464c52Smaya        items_count++;
14749f464c52Smaya    }
14759f464c52Smaya    bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
14769f464c52Smaya    return value_changed;
14779f464c52Smaya}
14789f464c52Smaya
14799f464c52Smaya//-------------------------------------------------------------------------
14809f464c52Smaya// [SECTION] Data Type and Data Formatting Helpers [Internal]
14819f464c52Smaya//-------------------------------------------------------------------------
14829f464c52Smaya// - PatchFormatStringFloatToInt()
14839f464c52Smaya// - DataTypeFormatString()
14849f464c52Smaya// - DataTypeApplyOp()
14859f464c52Smaya// - DataTypeApplyOpFromText()
14869f464c52Smaya// - GetMinimumStepAtDecimalPrecision
14879f464c52Smaya// - RoundScalarWithFormat<>()
14889f464c52Smaya//-------------------------------------------------------------------------
14899f464c52Smaya
14909f464c52Smayastruct ImGuiDataTypeInfo
14919f464c52Smaya{
14929f464c52Smaya    size_t      Size;
14939f464c52Smaya    const char* PrintFmt;   // Unused
14949f464c52Smaya    const char* ScanFmt;
14959f464c52Smaya};
14969f464c52Smaya
14979f464c52Smayastatic const ImGuiDataTypeInfo GDataTypeInfo[] =
14989f464c52Smaya{
14999f464c52Smaya    { sizeof(int),          "%d",   "%d"    },
15009f464c52Smaya    { sizeof(unsigned int), "%u",   "%u"    },
15019f464c52Smaya#ifdef _MSC_VER
15029f464c52Smaya    { sizeof(ImS64),        "%I64d","%I64d" },
15039f464c52Smaya    { sizeof(ImU64),        "%I64u","%I64u" },
15049f464c52Smaya#else
15059f464c52Smaya    { sizeof(ImS64),        "%lld", "%lld"  },
15069f464c52Smaya    { sizeof(ImU64),        "%llu", "%llu"  },
15079f464c52Smaya#endif
15089f464c52Smaya    { sizeof(float),        "%f",   "%f"    },  // float are promoted to double in va_arg
15099f464c52Smaya    { sizeof(double),       "%f",   "%lf"   },
15109f464c52Smaya};
15119f464c52SmayaIM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
15129f464c52Smaya
15139f464c52Smaya// 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".
15149f464c52Smaya// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
15159f464c52Smaya// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
15169f464c52Smayastatic const char* PatchFormatStringFloatToInt(const char* fmt)
15179f464c52Smaya{
15189f464c52Smaya    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.
15199f464c52Smaya        return "%d";
15209f464c52Smaya    const char* fmt_start = ImParseFormatFindStart(fmt);    // Find % (if any, and ignore %%)
15219f464c52Smaya    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).
15229f464c52Smaya    if (fmt_end > fmt_start && fmt_end[-1] == 'f')
15239f464c52Smaya    {
15249f464c52Smaya#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
15259f464c52Smaya        if (fmt_start == fmt && fmt_end[0] == 0)
15269f464c52Smaya            return "%d";
15279f464c52Smaya        ImGuiContext& g = *GImGui;
15289f464c52Smaya        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.
15299f464c52Smaya        return g.TempBuffer;
15309f464c52Smaya#else
15319f464c52Smaya        IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
15329f464c52Smaya#endif
15339f464c52Smaya    }
15349f464c52Smaya    return fmt;
15359f464c52Smaya}
15369f464c52Smaya
15379f464c52Smayastatic inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
15389f464c52Smaya{
15399f464c52Smaya    if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)   // Signedness doesn't matter when pushing the argument
15409f464c52Smaya        return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr);
15419f464c52Smaya    if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)   // Signedness doesn't matter when pushing the argument
15429f464c52Smaya        return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr);
15439f464c52Smaya    if (data_type == ImGuiDataType_Float)
15449f464c52Smaya        return ImFormatString(buf, buf_size, format, *(const float*)data_ptr);
15459f464c52Smaya    if (data_type == ImGuiDataType_Double)
15469f464c52Smaya        return ImFormatString(buf, buf_size, format, *(const double*)data_ptr);
15479f464c52Smaya    IM_ASSERT(0);
15489f464c52Smaya    return 0;
15499f464c52Smaya}
15509f464c52Smaya
15519f464c52Smaya// FIXME: Adding support for clamping on boundaries of the data type would be nice.
15529f464c52Smayastatic void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
15539f464c52Smaya{
15549f464c52Smaya    IM_ASSERT(op == '+' || op == '-');
15559f464c52Smaya    switch (data_type)
15569f464c52Smaya    {
15579f464c52Smaya        case ImGuiDataType_S32:
15589f464c52Smaya            if (op == '+')      *(int*)output = *(const int*)arg1 + *(const int*)arg2;
15599f464c52Smaya            else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;
15609f464c52Smaya            return;
15619f464c52Smaya        case ImGuiDataType_U32:
15629f464c52Smaya            if (op == '+')      *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;
15639f464c52Smaya            else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;
15649f464c52Smaya            return;
15659f464c52Smaya        case ImGuiDataType_S64:
15669f464c52Smaya            if (op == '+')      *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;
15679f464c52Smaya            else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;
15689f464c52Smaya            return;
15699f464c52Smaya        case ImGuiDataType_U64:
15709f464c52Smaya            if (op == '+')      *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;
15719f464c52Smaya            else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;
15729f464c52Smaya            return;
15739f464c52Smaya        case ImGuiDataType_Float:
15749f464c52Smaya            if (op == '+')      *(float*)output = *(const float*)arg1 + *(const float*)arg2;
15759f464c52Smaya            else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;
15769f464c52Smaya            return;
15779f464c52Smaya        case ImGuiDataType_Double:
15789f464c52Smaya            if (op == '+')      *(double*)output = *(const double*)arg1 + *(const double*)arg2;
15799f464c52Smaya            else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;
15809f464c52Smaya            return;
15819f464c52Smaya        case ImGuiDataType_COUNT: break;
15829f464c52Smaya    }
15839f464c52Smaya    IM_ASSERT(0);
15849f464c52Smaya}
15859f464c52Smaya
15869f464c52Smaya// User can input math operators (e.g. +100) to edit a numerical values.
15879f464c52Smaya// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
15889f464c52Smayastatic bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)
15899f464c52Smaya{
15909f464c52Smaya    while (ImCharIsBlankA(*buf))
15919f464c52Smaya        buf++;
15929f464c52Smaya
15939f464c52Smaya    // We don't support '-' op because it would conflict with inputing negative value.
15949f464c52Smaya    // Instead you can use +-100 to subtract from an existing value
15959f464c52Smaya    char op = buf[0];
15969f464c52Smaya    if (op == '+' || op == '*' || op == '/')
15979f464c52Smaya    {
15989f464c52Smaya        buf++;
15999f464c52Smaya        while (ImCharIsBlankA(*buf))
16009f464c52Smaya            buf++;
16019f464c52Smaya    }
16029f464c52Smaya    else
16039f464c52Smaya    {
16049f464c52Smaya        op = 0;
16059f464c52Smaya    }
16069f464c52Smaya    if (!buf[0])
16079f464c52Smaya        return false;
16089f464c52Smaya
16099f464c52Smaya    // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
16109f464c52Smaya    IM_ASSERT(data_type < ImGuiDataType_COUNT);
16119f464c52Smaya    int data_backup[2];
16129f464c52Smaya    IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
16139f464c52Smaya    memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);
16149f464c52Smaya
16159f464c52Smaya    if (format == NULL)
16169f464c52Smaya        format = GDataTypeInfo[data_type].ScanFmt;
16179f464c52Smaya
16189f464c52Smaya    int arg1i = 0;
16199f464c52Smaya    if (data_type == ImGuiDataType_S32)
16209f464c52Smaya    {
16219f464c52Smaya        int* v = (int*)data_ptr;
16229f464c52Smaya        int arg0i = *v;
16239f464c52Smaya        float arg1f = 0.0f;
16249f464c52Smaya        if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
16259f464c52Smaya            return false;
16269f464c52Smaya        // 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
16279f464c52Smaya        if (op == '+')      { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); }                   // Add (use "+-" to subtract)
16289f464c52Smaya        else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); }                   // Multiply
16299f464c52Smaya        else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }  // Divide
16309f464c52Smaya        else                { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; }                           // Assign constant
16319f464c52Smaya    }
16329f464c52Smaya    else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
16339f464c52Smaya    {
16349f464c52Smaya        // Assign constant
16359f464c52Smaya        // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
16369f464c52Smaya        sscanf(buf, format, data_ptr);
16379f464c52Smaya    }
16389f464c52Smaya    else if (data_type == ImGuiDataType_Float)
16399f464c52Smaya    {
16409f464c52Smaya        // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
16419f464c52Smaya        format = "%f";
16429f464c52Smaya        float* v = (float*)data_ptr;
16439f464c52Smaya        float arg0f = *v, arg1f = 0.0f;
16449f464c52Smaya        if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
16459f464c52Smaya            return false;
16469f464c52Smaya        if (sscanf(buf, format, &arg1f) < 1)
16479f464c52Smaya            return false;
16489f464c52Smaya        if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
16499f464c52Smaya        else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
16509f464c52Smaya        else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
16519f464c52Smaya        else                { *v = arg1f; }                            // Assign constant
16529f464c52Smaya    }
16539f464c52Smaya    else if (data_type == ImGuiDataType_Double)
16549f464c52Smaya    {
16559f464c52Smaya        format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
16569f464c52Smaya        double* v = (double*)data_ptr;
16579f464c52Smaya        double arg0f = *v, arg1f = 0.0;
16589f464c52Smaya        if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
16599f464c52Smaya            return false;
16609f464c52Smaya        if (sscanf(buf, format, &arg1f) < 1)
16619f464c52Smaya            return false;
16629f464c52Smaya        if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
16639f464c52Smaya        else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
16649f464c52Smaya        else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
16659f464c52Smaya        else                { *v = arg1f; }                            // Assign constant
16669f464c52Smaya    }
16679f464c52Smaya    return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;
16689f464c52Smaya}
16699f464c52Smaya
16709f464c52Smayastatic float GetMinimumStepAtDecimalPrecision(int decimal_precision)
16719f464c52Smaya{
16729f464c52Smaya    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 };
16739f464c52Smaya    if (decimal_precision < 0)
16749f464c52Smaya        return FLT_MIN;
16759f464c52Smaya    return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
16769f464c52Smaya}
16779f464c52Smaya
16789f464c52Smayatemplate<typename TYPE>
16799f464c52Smayastatic const char* ImAtoi(const char* src, TYPE* output)
16809f464c52Smaya{
16819f464c52Smaya    int negative = 0;
16829f464c52Smaya    if (*src == '-') { negative = 1; src++; }
16839f464c52Smaya    if (*src == '+') { src++; }
16849f464c52Smaya    TYPE v = 0;
16859f464c52Smaya    while (*src >= '0' && *src <= '9')
16869f464c52Smaya        v = (v * 10) + (*src++ - '0');
16879f464c52Smaya    *output = negative ? -v : v;
16889f464c52Smaya    return src;
16899f464c52Smaya}
16909f464c52Smaya
16919f464c52Smayatemplate<typename TYPE, typename SIGNEDTYPE>
16929f464c52SmayaTYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
16939f464c52Smaya{
16949f464c52Smaya    const char* fmt_start = ImParseFormatFindStart(format);
16959f464c52Smaya    if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
16969f464c52Smaya        return v;
16979f464c52Smaya    char v_str[64];
16989f464c52Smaya    ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
16999f464c52Smaya    const char* p = v_str;
17009f464c52Smaya    while (*p == ' ')
17019f464c52Smaya        p++;
17029f464c52Smaya    if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
17039f464c52Smaya        v = (TYPE)ImAtof(p);
17049f464c52Smaya    else
17059f464c52Smaya        ImAtoi(p, (SIGNEDTYPE*)&v);
17069f464c52Smaya    return v;
17079f464c52Smaya}
17089f464c52Smaya
17099f464c52Smaya//-------------------------------------------------------------------------
17109f464c52Smaya// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
17119f464c52Smaya//-------------------------------------------------------------------------
17129f464c52Smaya// - DragBehaviorT<>() [Internal]
17139f464c52Smaya// - DragBehavior() [Internal]
17149f464c52Smaya// - DragScalar()
17159f464c52Smaya// - DragScalarN()
17169f464c52Smaya// - DragFloat()
17179f464c52Smaya// - DragFloat2()
17189f464c52Smaya// - DragFloat3()
17199f464c52Smaya// - DragFloat4()
17209f464c52Smaya// - DragFloatRange2()
17219f464c52Smaya// - DragInt()
17229f464c52Smaya// - DragInt2()
17239f464c52Smaya// - DragInt3()
17249f464c52Smaya// - DragInt4()
17259f464c52Smaya// - DragIntRange2()
17269f464c52Smaya//-------------------------------------------------------------------------
17279f464c52Smaya
17289f464c52Smaya// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
17299f464c52Smayatemplate<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
17309f464c52Smayabool 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)
17319f464c52Smaya{
17329f464c52Smaya    ImGuiContext& g = *GImGui;
17339f464c52Smaya    const ImGuiAxis axis = (flags & ImGuiDragFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
17349f464c52Smaya    const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
17359f464c52Smaya    const bool has_min_max = (v_min != v_max);
17369f464c52Smaya    const bool is_power = (power != 1.0f && is_decimal && has_min_max && (v_max - v_min < FLT_MAX));
17379f464c52Smaya
17389f464c52Smaya    // Default tweak speed
17399f464c52Smaya    if (v_speed == 0.0f && has_min_max && (v_max - v_min < FLT_MAX))
17409f464c52Smaya        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
17419f464c52Smaya
17429f464c52Smaya    // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
17439f464c52Smaya    float adjust_delta = 0.0f;
17449f464c52Smaya    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
17459f464c52Smaya    {
17469f464c52Smaya        adjust_delta = g.IO.MouseDelta[axis];
17479f464c52Smaya        if (g.IO.KeyAlt)
17489f464c52Smaya            adjust_delta *= 1.0f / 100.0f;
17499f464c52Smaya        if (g.IO.KeyShift)
17509f464c52Smaya            adjust_delta *= 10.0f;
17519f464c52Smaya    }
17529f464c52Smaya    else if (g.ActiveIdSource == ImGuiInputSource_Nav)
17539f464c52Smaya    {
17549f464c52Smaya        int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
17559f464c52Smaya        adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
17569f464c52Smaya        v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
17579f464c52Smaya    }
17589f464c52Smaya    adjust_delta *= v_speed;
17599f464c52Smaya
17609f464c52Smaya    // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
17619f464c52Smaya    if (axis == ImGuiAxis_Y)
17629f464c52Smaya        adjust_delta = -adjust_delta;
17639f464c52Smaya
17649f464c52Smaya    // Clear current value on activation
17659f464c52Smaya    // 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.
17669f464c52Smaya    bool is_just_activated = g.ActiveIdIsJustActivated;
17679f464c52Smaya    bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
17689f464c52Smaya    bool is_drag_direction_change_with_power = is_power && ((adjust_delta < 0 && g.DragCurrentAccum > 0) || (adjust_delta > 0 && g.DragCurrentAccum < 0));
17699f464c52Smaya    if (is_just_activated || is_already_past_limits_and_pushing_outward || is_drag_direction_change_with_power)
17709f464c52Smaya    {
17719f464c52Smaya        g.DragCurrentAccum = 0.0f;
17729f464c52Smaya        g.DragCurrentAccumDirty = false;
17739f464c52Smaya    }
17749f464c52Smaya    else if (adjust_delta != 0.0f)
17759f464c52Smaya    {
17769f464c52Smaya        g.DragCurrentAccum += adjust_delta;
17779f464c52Smaya        g.DragCurrentAccumDirty = true;
17789f464c52Smaya    }
17799f464c52Smaya
17809f464c52Smaya    if (!g.DragCurrentAccumDirty)
17819f464c52Smaya        return false;
17829f464c52Smaya
17839f464c52Smaya    TYPE v_cur = *v;
17849f464c52Smaya    FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
17859f464c52Smaya
17869f464c52Smaya    if (is_power)
17879f464c52Smaya    {
17889f464c52Smaya        // 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
17899f464c52Smaya        FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
17909f464c52Smaya        FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
17919f464c52Smaya        v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);
17929f464c52Smaya        v_old_ref_for_accum_remainder = v_old_norm_curved;
17939f464c52Smaya    }
17949f464c52Smaya    else
17959f464c52Smaya    {
17969f464c52Smaya        v_cur += (TYPE)g.DragCurrentAccum;
17979f464c52Smaya    }
17989f464c52Smaya
17999f464c52Smaya    // Round to user desired precision based on format string
18009f464c52Smaya    v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
18019f464c52Smaya
18029f464c52Smaya    // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
18039f464c52Smaya    g.DragCurrentAccumDirty = false;
18049f464c52Smaya    if (is_power)
18059f464c52Smaya    {
18069f464c52Smaya        FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
18079f464c52Smaya        g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
18089f464c52Smaya    }
18099f464c52Smaya    else
18109f464c52Smaya    {
18119f464c52Smaya        g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
18129f464c52Smaya    }
18139f464c52Smaya
18149f464c52Smaya    // Lose zero sign for float/double
18159f464c52Smaya    if (v_cur == (TYPE)-0)
18169f464c52Smaya        v_cur = (TYPE)0;
18179f464c52Smaya
18189f464c52Smaya    // Clamp values (+ handle overflow/wrap-around for integer types)
18199f464c52Smaya    if (*v != v_cur && has_min_max)
18209f464c52Smaya    {
18219f464c52Smaya        if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal))
18229f464c52Smaya            v_cur = v_min;
18239f464c52Smaya        if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal))
18249f464c52Smaya            v_cur = v_max;
18259f464c52Smaya    }
18269f464c52Smaya
18279f464c52Smaya    // Apply result
18289f464c52Smaya    if (*v == v_cur)
18299f464c52Smaya        return false;
18309f464c52Smaya    *v = v_cur;
18319f464c52Smaya    return true;
18329f464c52Smaya}
18339f464c52Smaya
18349f464c52Smayabool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power, ImGuiDragFlags flags)
18359f464c52Smaya{
18369f464c52Smaya    ImGuiContext& g = *GImGui;
18379f464c52Smaya    if (g.ActiveId == id)
18389f464c52Smaya    {
18399f464c52Smaya        if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
18409f464c52Smaya            ClearActiveID();
18419f464c52Smaya        else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
18429f464c52Smaya            ClearActiveID();
18439f464c52Smaya    }
18449f464c52Smaya    if (g.ActiveId != id)
18459f464c52Smaya        return false;
18469f464c52Smaya
18479f464c52Smaya    switch (data_type)
18489f464c52Smaya    {
18499f464c52Smaya    case ImGuiDataType_S32:    return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)v,  v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power, flags);
18509f464c52Smaya    case ImGuiDataType_U32:    return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)v,  v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power, flags);
18519f464c52Smaya    case ImGuiDataType_S64:    return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)v,  v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power, flags);
18529f464c52Smaya    case ImGuiDataType_U64:    return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)v,  v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power, flags);
18539f464c52Smaya    case ImGuiDataType_Float:  return DragBehaviorT<float, float, float >(data_type, (float*)v,  v_speed, v_min ? *(const float* )v_min : -FLT_MAX,   v_max ? *(const float* )v_max : FLT_MAX,    format, power, flags);
18549f464c52Smaya    case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX,   v_max ? *(const double*)v_max : DBL_MAX,    format, power, flags);
18559f464c52Smaya    case ImGuiDataType_COUNT:  break;
18569f464c52Smaya    }
18579f464c52Smaya    IM_ASSERT(0);
18589f464c52Smaya    return false;
18599f464c52Smaya}
18609f464c52Smaya
18619f464c52Smayabool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
18629f464c52Smaya{
18639f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
18649f464c52Smaya    if (window->SkipItems)
18659f464c52Smaya        return false;
18669f464c52Smaya
18679f464c52Smaya    if (power != 1.0f)
18689f464c52Smaya        IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
18699f464c52Smaya
18709f464c52Smaya    ImGuiContext& g = *GImGui;
18719f464c52Smaya    const ImGuiStyle& style = g.Style;
18729f464c52Smaya    const ImGuiID id = window->GetID(label);
18739f464c52Smaya    const float w = CalcItemWidth();
18749f464c52Smaya
18759f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
18769f464c52Smaya    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
18779f464c52Smaya    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));
18789f464c52Smaya
18799f464c52Smaya    ItemSize(total_bb, style.FramePadding.y);
18809f464c52Smaya    if (!ItemAdd(total_bb, id, &frame_bb))
18819f464c52Smaya        return false;
18829f464c52Smaya
18839f464c52Smaya    const bool hovered = ItemHoverable(frame_bb, id);
18849f464c52Smaya
18859f464c52Smaya    // Default format string when passing NULL
18869f464c52Smaya    // Patch old "%.0f" format string to use "%d", read function comments for more details.
18879f464c52Smaya    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
18889f464c52Smaya    if (format == NULL)
18899f464c52Smaya        format = GDataTypeInfo[data_type].PrintFmt;
18909f464c52Smaya    else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
18919f464c52Smaya        format = PatchFormatStringFloatToInt(format);
18929f464c52Smaya
18939f464c52Smaya    // Tabbing or CTRL-clicking on Drag turns it into an input box
18949f464c52Smaya    bool start_text_input = false;
18959f464c52Smaya    const bool tab_focus_requested = FocusableItemRegister(window, id);
18969f464c52Smaya    if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
18979f464c52Smaya    {
18989f464c52Smaya        SetActiveID(id, window);
18999f464c52Smaya        SetFocusID(id, window);
19009f464c52Smaya        FocusWindow(window);
19019f464c52Smaya        g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
19029f464c52Smaya        if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
19039f464c52Smaya        {
19049f464c52Smaya            start_text_input = true;
19059f464c52Smaya            g.ScalarAsInputTextId = 0;
19069f464c52Smaya        }
19079f464c52Smaya    }
19089f464c52Smaya    if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
19099f464c52Smaya    {
19109f464c52Smaya        window->DC.CursorPos = frame_bb.Min;
19119f464c52Smaya        FocusableItemUnregister(window);
19129f464c52Smaya        return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
19139f464c52Smaya    }
19149f464c52Smaya
19159f464c52Smaya    // Actual drag behavior
19169f464c52Smaya    const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power, ImGuiDragFlags_None);
19179f464c52Smaya    if (value_changed)
19189f464c52Smaya        MarkItemEdited(id);
19199f464c52Smaya
19209f464c52Smaya    // Draw frame
19219f464c52Smaya    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
19229f464c52Smaya    RenderNavHighlight(frame_bb, id);
19239f464c52Smaya    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
19249f464c52Smaya
19259f464c52Smaya    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
19269f464c52Smaya    char value_buf[64];
19279f464c52Smaya    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
19289f464c52Smaya    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
19299f464c52Smaya
19309f464c52Smaya    if (label_size.x > 0.0f)
19319f464c52Smaya        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
19329f464c52Smaya
19339f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
19349f464c52Smaya    return value_changed;
19359f464c52Smaya}
19369f464c52Smaya
19379f464c52Smayabool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
19389f464c52Smaya{
19399f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
19409f464c52Smaya    if (window->SkipItems)
19419f464c52Smaya        return false;
19429f464c52Smaya
19439f464c52Smaya    ImGuiContext& g = *GImGui;
19449f464c52Smaya    bool value_changed = false;
19459f464c52Smaya    BeginGroup();
19469f464c52Smaya    PushID(label);
19479f464c52Smaya    PushMultiItemsWidths(components);
19489f464c52Smaya    size_t type_size = GDataTypeInfo[data_type].Size;
19499f464c52Smaya    for (int i = 0; i < components; i++)
19509f464c52Smaya    {
19519f464c52Smaya        PushID(i);
19529f464c52Smaya        value_changed |= DragScalar("", data_type, v, v_speed, v_min, v_max, format, power);
19539f464c52Smaya        SameLine(0, g.Style.ItemInnerSpacing.x);
19549f464c52Smaya        PopID();
19559f464c52Smaya        PopItemWidth();
19569f464c52Smaya        v = (void*)((char*)v + type_size);
19579f464c52Smaya    }
19589f464c52Smaya    PopID();
19599f464c52Smaya
19609f464c52Smaya    TextUnformatted(label, FindRenderedTextEnd(label));
19619f464c52Smaya    EndGroup();
19629f464c52Smaya    return value_changed;
19639f464c52Smaya}
19649f464c52Smaya
19659f464c52Smayabool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
19669f464c52Smaya{
19679f464c52Smaya    return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
19689f464c52Smaya}
19699f464c52Smaya
19709f464c52Smayabool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
19719f464c52Smaya{
19729f464c52Smaya    return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
19739f464c52Smaya}
19749f464c52Smaya
19759f464c52Smayabool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
19769f464c52Smaya{
19779f464c52Smaya    return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
19789f464c52Smaya}
19799f464c52Smaya
19809f464c52Smayabool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
19819f464c52Smaya{
19829f464c52Smaya    return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
19839f464c52Smaya}
19849f464c52Smaya
19859f464c52Smayabool 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)
19869f464c52Smaya{
19879f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
19889f464c52Smaya    if (window->SkipItems)
19899f464c52Smaya        return false;
19909f464c52Smaya
19919f464c52Smaya    ImGuiContext& g = *GImGui;
19929f464c52Smaya    PushID(label);
19939f464c52Smaya    BeginGroup();
19949f464c52Smaya    PushMultiItemsWidths(2);
19959f464c52Smaya
19969f464c52Smaya    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);
19979f464c52Smaya    PopItemWidth();
19989f464c52Smaya    SameLine(0, g.Style.ItemInnerSpacing.x);
19999f464c52Smaya    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);
20009f464c52Smaya    PopItemWidth();
20019f464c52Smaya    SameLine(0, g.Style.ItemInnerSpacing.x);
20029f464c52Smaya
20039f464c52Smaya    TextUnformatted(label, FindRenderedTextEnd(label));
20049f464c52Smaya    EndGroup();
20059f464c52Smaya    PopID();
20069f464c52Smaya    return value_changed;
20079f464c52Smaya}
20089f464c52Smaya
20099f464c52Smaya// NB: v_speed is float to allow adjusting the drag speed with more precision
20109f464c52Smayabool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
20119f464c52Smaya{
20129f464c52Smaya    return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
20139f464c52Smaya}
20149f464c52Smaya
20159f464c52Smayabool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
20169f464c52Smaya{
20179f464c52Smaya    return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
20189f464c52Smaya}
20199f464c52Smaya
20209f464c52Smayabool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
20219f464c52Smaya{
20229f464c52Smaya    return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
20239f464c52Smaya}
20249f464c52Smaya
20259f464c52Smayabool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
20269f464c52Smaya{
20279f464c52Smaya    return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
20289f464c52Smaya}
20299f464c52Smaya
20309f464c52Smayabool 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)
20319f464c52Smaya{
20329f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
20339f464c52Smaya    if (window->SkipItems)
20349f464c52Smaya        return false;
20359f464c52Smaya
20369f464c52Smaya    ImGuiContext& g = *GImGui;
20379f464c52Smaya    PushID(label);
20389f464c52Smaya    BeginGroup();
20399f464c52Smaya    PushMultiItemsWidths(2);
20409f464c52Smaya
20419f464c52Smaya    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);
20429f464c52Smaya    PopItemWidth();
20439f464c52Smaya    SameLine(0, g.Style.ItemInnerSpacing.x);
20449f464c52Smaya    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);
20459f464c52Smaya    PopItemWidth();
20469f464c52Smaya    SameLine(0, g.Style.ItemInnerSpacing.x);
20479f464c52Smaya
20489f464c52Smaya    TextUnformatted(label, FindRenderedTextEnd(label));
20499f464c52Smaya    EndGroup();
20509f464c52Smaya    PopID();
20519f464c52Smaya
20529f464c52Smaya    return value_changed;
20539f464c52Smaya}
20549f464c52Smaya
20559f464c52Smaya//-------------------------------------------------------------------------
20569f464c52Smaya// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
20579f464c52Smaya//-------------------------------------------------------------------------
20589f464c52Smaya// - SliderBehaviorT<>() [Internal]
20599f464c52Smaya// - SliderBehavior() [Internal]
20609f464c52Smaya// - SliderScalar()
20619f464c52Smaya// - SliderScalarN()
20629f464c52Smaya// - SliderFloat()
20639f464c52Smaya// - SliderFloat2()
20649f464c52Smaya// - SliderFloat3()
20659f464c52Smaya// - SliderFloat4()
20669f464c52Smaya// - SliderAngle()
20679f464c52Smaya// - SliderInt()
20689f464c52Smaya// - SliderInt2()
20699f464c52Smaya// - SliderInt3()
20709f464c52Smaya// - SliderInt4()
20719f464c52Smaya// - VSliderScalar()
20729f464c52Smaya// - VSliderFloat()
20739f464c52Smaya// - VSliderInt()
20749f464c52Smaya//-------------------------------------------------------------------------
20759f464c52Smaya
20769f464c52Smayatemplate<typename TYPE, typename FLOATTYPE>
20779f464c52Smayafloat ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
20789f464c52Smaya{
20799f464c52Smaya    if (v_min == v_max)
20809f464c52Smaya        return 0.0f;
20819f464c52Smaya
20829f464c52Smaya    const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
20839f464c52Smaya    const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
20849f464c52Smaya    if (is_power)
20859f464c52Smaya    {
20869f464c52Smaya        if (v_clamped < 0.0f)
20879f464c52Smaya        {
20889f464c52Smaya            const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
20899f464c52Smaya            return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;
20909f464c52Smaya        }
20919f464c52Smaya        else
20929f464c52Smaya        {
20939f464c52Smaya            const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
20949f464c52Smaya            return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);
20959f464c52Smaya        }
20969f464c52Smaya    }
20979f464c52Smaya
20989f464c52Smaya    // Linear slider
20999f464c52Smaya    return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
21009f464c52Smaya}
21019f464c52Smaya
21029f464c52Smaya// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
21039f464c52Smayatemplate<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
21049f464c52Smayabool 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)
21059f464c52Smaya{
21069f464c52Smaya    ImGuiContext& g = *GImGui;
21079f464c52Smaya    const ImGuiStyle& style = g.Style;
21089f464c52Smaya
21099f464c52Smaya    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
21109f464c52Smaya    const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
21119f464c52Smaya    const bool is_power = (power != 1.0f) && is_decimal;
21129f464c52Smaya
21139f464c52Smaya    const float grab_padding = 2.0f;
21149f464c52Smaya    const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
21159f464c52Smaya    float grab_sz = style.GrabMinSize;
21169f464c52Smaya    SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
21179f464c52Smaya    if (!is_decimal && v_range >= 0)                                             // v_range < 0 may happen on integer overflows
21189f464c52Smaya        grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize);  // For integer sliders: if possible have the grab size represent 1 unit
21199f464c52Smaya    grab_sz = ImMin(grab_sz, slider_sz);
21209f464c52Smaya    const float slider_usable_sz = slider_sz - grab_sz;
21219f464c52Smaya    const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz*0.5f;
21229f464c52Smaya    const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz*0.5f;
21239f464c52Smaya
21249f464c52Smaya    // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
21259f464c52Smaya    float linear_zero_pos;   // 0.0->1.0f
21269f464c52Smaya    if (is_power && v_min * v_max < 0.0f)
21279f464c52Smaya    {
21289f464c52Smaya        // Different sign
21299f464c52Smaya        const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);
21309f464c52Smaya        const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);
21319f464c52Smaya        linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
21329f464c52Smaya    }
21339f464c52Smaya    else
21349f464c52Smaya    {
21359f464c52Smaya        // Same sign
21369f464c52Smaya        linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
21379f464c52Smaya    }
21389f464c52Smaya
21399f464c52Smaya    // Process interacting with the slider
21409f464c52Smaya    bool value_changed = false;
21419f464c52Smaya    if (g.ActiveId == id)
21429f464c52Smaya    {
21439f464c52Smaya        bool set_new_value = false;
21449f464c52Smaya        float clicked_t = 0.0f;
21459f464c52Smaya        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
21469f464c52Smaya        {
21479f464c52Smaya            if (!g.IO.MouseDown[0])
21489f464c52Smaya            {
21499f464c52Smaya                ClearActiveID();
21509f464c52Smaya            }
21519f464c52Smaya            else
21529f464c52Smaya            {
21539f464c52Smaya                const float mouse_abs_pos = g.IO.MousePos[axis];
21549f464c52Smaya                clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
21559f464c52Smaya                if (axis == ImGuiAxis_Y)
21569f464c52Smaya                    clicked_t = 1.0f - clicked_t;
21579f464c52Smaya                set_new_value = true;
21589f464c52Smaya            }
21599f464c52Smaya        }
21609f464c52Smaya        else if (g.ActiveIdSource == ImGuiInputSource_Nav)
21619f464c52Smaya        {
21629f464c52Smaya            const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
21639f464c52Smaya            float delta = (axis == ImGuiAxis_X) ? delta2.x : -delta2.y;
21649f464c52Smaya            if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
21659f464c52Smaya            {
21669f464c52Smaya                ClearActiveID();
21679f464c52Smaya            }
21689f464c52Smaya            else if (delta != 0.0f)
21699f464c52Smaya            {
21709f464c52Smaya                clicked_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
21719f464c52Smaya                const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
21729f464c52Smaya                if ((decimal_precision > 0) || is_power)
21739f464c52Smaya                {
21749f464c52Smaya                    delta /= 100.0f;    // Gamepad/keyboard tweak speeds in % of slider bounds
21759f464c52Smaya                    if (IsNavInputDown(ImGuiNavInput_TweakSlow))
21769f464c52Smaya                        delta /= 10.0f;
21779f464c52Smaya                }
21789f464c52Smaya                else
21799f464c52Smaya                {
21809f464c52Smaya                    if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
21819f464c52Smaya                        delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
21829f464c52Smaya                    else
21839f464c52Smaya                        delta /= 100.0f;
21849f464c52Smaya                }
21859f464c52Smaya                if (IsNavInputDown(ImGuiNavInput_TweakFast))
21869f464c52Smaya                    delta *= 10.0f;
21879f464c52Smaya                set_new_value = true;
21889f464c52Smaya                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
21899f464c52Smaya                    set_new_value = false;
21909f464c52Smaya                else
21919f464c52Smaya                    clicked_t = ImSaturate(clicked_t + delta);
21929f464c52Smaya            }
21939f464c52Smaya        }
21949f464c52Smaya
21959f464c52Smaya        if (set_new_value)
21969f464c52Smaya        {
21979f464c52Smaya            TYPE v_new;
21989f464c52Smaya            if (is_power)
21999f464c52Smaya            {
22009f464c52Smaya                // Account for power curve scale on both sides of the zero
22019f464c52Smaya                if (clicked_t < linear_zero_pos)
22029f464c52Smaya                {
22039f464c52Smaya                    // Negative: rescale to the negative range before powering
22049f464c52Smaya                    float a = 1.0f - (clicked_t / linear_zero_pos);
22059f464c52Smaya                    a = ImPow(a, power);
22069f464c52Smaya                    v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
22079f464c52Smaya                }
22089f464c52Smaya                else
22099f464c52Smaya                {
22109f464c52Smaya                    // Positive: rescale to the positive range before powering
22119f464c52Smaya                    float a;
22129f464c52Smaya                    if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
22139f464c52Smaya                        a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
22149f464c52Smaya                    else
22159f464c52Smaya                        a = clicked_t;
22169f464c52Smaya                    a = ImPow(a, power);
22179f464c52Smaya                    v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
22189f464c52Smaya                }
22199f464c52Smaya            }
22209f464c52Smaya            else
22219f464c52Smaya            {
22229f464c52Smaya                // Linear slider
22239f464c52Smaya                if (is_decimal)
22249f464c52Smaya                {
22259f464c52Smaya                    v_new = ImLerp(v_min, v_max, clicked_t);
22269f464c52Smaya                }
22279f464c52Smaya                else
22289f464c52Smaya                {
22299f464c52Smaya                    // For integer values we want the clicking position to match the grab box so we round above
22309f464c52Smaya                    // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
22319f464c52Smaya                    FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
22329f464c52Smaya                    TYPE v_new_off_floor = (TYPE)(v_new_off_f);
22339f464c52Smaya                    TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
22349f464c52Smaya                    if (!is_decimal && v_new_off_floor < v_new_off_round)
22359f464c52Smaya                        v_new = v_min + v_new_off_round;
22369f464c52Smaya                    else
22379f464c52Smaya                        v_new = v_min + v_new_off_floor;
22389f464c52Smaya                }
22399f464c52Smaya            }
22409f464c52Smaya
22419f464c52Smaya            // Round to user desired precision based on format string
22429f464c52Smaya            v_new = RoundScalarWithFormatT<TYPE,SIGNEDTYPE>(format, data_type, v_new);
22439f464c52Smaya
22449f464c52Smaya            // Apply result
22459f464c52Smaya            if (*v != v_new)
22469f464c52Smaya            {
22479f464c52Smaya                *v = v_new;
22489f464c52Smaya                value_changed = true;
22499f464c52Smaya            }
22509f464c52Smaya        }
22519f464c52Smaya    }
22529f464c52Smaya
22539f464c52Smaya    // Output grab position so it can be displayed by the caller
22549f464c52Smaya    float grab_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
22559f464c52Smaya    if (axis == ImGuiAxis_Y)
22569f464c52Smaya        grab_t = 1.0f - grab_t;
22579f464c52Smaya    const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
22589f464c52Smaya    if (axis == ImGuiAxis_X)
22599f464c52Smaya        *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);
22609f464c52Smaya    else
22619f464c52Smaya        *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);
22629f464c52Smaya
22639f464c52Smaya    return value_changed;
22649f464c52Smaya}
22659f464c52Smaya
22669f464c52Smaya// For 32-bits and larger types, slider bounds are limited to half the natural type range.
22679f464c52Smaya// 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.
22689f464c52Smaya// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
22699f464c52Smayabool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
22709f464c52Smaya{
22719f464c52Smaya    switch (data_type)
22729f464c52Smaya    {
22739f464c52Smaya    case ImGuiDataType_S32:
22749f464c52Smaya        IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);
22759f464c52Smaya        return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v,  *(const ImS32*)v_min,  *(const ImS32*)v_max,  format, power, flags, out_grab_bb);
22769f464c52Smaya    case ImGuiDataType_U32:
22779f464c52Smaya        IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);
22789f464c52Smaya        return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v,  *(const ImU32*)v_min,  *(const ImU32*)v_max,  format, power, flags, out_grab_bb);
22799f464c52Smaya    case ImGuiDataType_S64:
22809f464c52Smaya        IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);
22819f464c52Smaya        return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v,  *(const ImS64*)v_min,  *(const ImS64*)v_max,  format, power, flags, out_grab_bb);
22829f464c52Smaya    case ImGuiDataType_U64:
22839f464c52Smaya        IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);
22849f464c52Smaya        return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v,  *(const ImU64*)v_min,  *(const ImU64*)v_max,  format, power, flags, out_grab_bb);
22859f464c52Smaya    case ImGuiDataType_Float:
22869f464c52Smaya        IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);
22879f464c52Smaya        return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v,  *(const float*)v_min,  *(const float*)v_max,  format, power, flags, out_grab_bb);
22889f464c52Smaya    case ImGuiDataType_Double:
22899f464c52Smaya        IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);
22909f464c52Smaya        return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb);
22919f464c52Smaya    case ImGuiDataType_COUNT: break;
22929f464c52Smaya    }
22939f464c52Smaya    IM_ASSERT(0);
22949f464c52Smaya    return false;
22959f464c52Smaya}
22969f464c52Smaya
22979f464c52Smayabool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
22989f464c52Smaya{
22999f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
23009f464c52Smaya    if (window->SkipItems)
23019f464c52Smaya        return false;
23029f464c52Smaya
23039f464c52Smaya    ImGuiContext& g = *GImGui;
23049f464c52Smaya    const ImGuiStyle& style = g.Style;
23059f464c52Smaya    const ImGuiID id = window->GetID(label);
23069f464c52Smaya    const float w = CalcItemWidth();
23079f464c52Smaya
23089f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
23099f464c52Smaya    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
23109f464c52Smaya    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));
23119f464c52Smaya
23129f464c52Smaya    ItemSize(total_bb, style.FramePadding.y);
23139f464c52Smaya    if (!ItemAdd(total_bb, id, &frame_bb))
23149f464c52Smaya        return false;
23159f464c52Smaya
23169f464c52Smaya    // Default format string when passing NULL
23179f464c52Smaya    // Patch old "%.0f" format string to use "%d", read function comments for more details.
23189f464c52Smaya    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
23199f464c52Smaya    if (format == NULL)
23209f464c52Smaya        format = GDataTypeInfo[data_type].PrintFmt;
23219f464c52Smaya    else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
23229f464c52Smaya        format = PatchFormatStringFloatToInt(format);
23239f464c52Smaya
23249f464c52Smaya    // Tabbing or CTRL-clicking on Slider turns it into an input box
23259f464c52Smaya    bool start_text_input = false;
23269f464c52Smaya    const bool tab_focus_requested = FocusableItemRegister(window, id);
23279f464c52Smaya    const bool hovered = ItemHoverable(frame_bb, id);
23289f464c52Smaya    if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
23299f464c52Smaya    {
23309f464c52Smaya        SetActiveID(id, window);
23319f464c52Smaya        SetFocusID(id, window);
23329f464c52Smaya        FocusWindow(window);
23339f464c52Smaya        g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
23349f464c52Smaya        if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
23359f464c52Smaya        {
23369f464c52Smaya            start_text_input = true;
23379f464c52Smaya            g.ScalarAsInputTextId = 0;
23389f464c52Smaya        }
23399f464c52Smaya    }
23409f464c52Smaya    if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
23419f464c52Smaya    {
23429f464c52Smaya        window->DC.CursorPos = frame_bb.Min;
23439f464c52Smaya        FocusableItemUnregister(window);
23449f464c52Smaya        return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
23459f464c52Smaya    }
23469f464c52Smaya
23479f464c52Smaya    // Draw frame
23489f464c52Smaya    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
23499f464c52Smaya    RenderNavHighlight(frame_bb, id);
23509f464c52Smaya    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
23519f464c52Smaya
23529f464c52Smaya    // Slider behavior
23539f464c52Smaya    ImRect grab_bb;
23549f464c52Smaya    const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb);
23559f464c52Smaya    if (value_changed)
23569f464c52Smaya        MarkItemEdited(id);
23579f464c52Smaya
23589f464c52Smaya    // Render grab
23599f464c52Smaya    window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
23609f464c52Smaya
23619f464c52Smaya    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
23629f464c52Smaya    char value_buf[64];
23639f464c52Smaya    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
23649f464c52Smaya    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
23659f464c52Smaya
23669f464c52Smaya    if (label_size.x > 0.0f)
23679f464c52Smaya        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
23689f464c52Smaya
23699f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
23709f464c52Smaya    return value_changed;
23719f464c52Smaya}
23729f464c52Smaya
23739f464c52Smaya// Add multiple sliders on 1 line for compact edition of multiple components
23749f464c52Smayabool 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)
23759f464c52Smaya{
23769f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
23779f464c52Smaya    if (window->SkipItems)
23789f464c52Smaya        return false;
23799f464c52Smaya
23809f464c52Smaya    ImGuiContext& g = *GImGui;
23819f464c52Smaya    bool value_changed = false;
23829f464c52Smaya    BeginGroup();
23839f464c52Smaya    PushID(label);
23849f464c52Smaya    PushMultiItemsWidths(components);
23859f464c52Smaya    size_t type_size = GDataTypeInfo[data_type].Size;
23869f464c52Smaya    for (int i = 0; i < components; i++)
23879f464c52Smaya    {
23889f464c52Smaya        PushID(i);
23899f464c52Smaya        value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, power);
23909f464c52Smaya        SameLine(0, g.Style.ItemInnerSpacing.x);
23919f464c52Smaya        PopID();
23929f464c52Smaya        PopItemWidth();
23939f464c52Smaya        v = (void*)((char*)v + type_size);
23949f464c52Smaya    }
23959f464c52Smaya    PopID();
23969f464c52Smaya
23979f464c52Smaya    TextUnformatted(label, FindRenderedTextEnd(label));
23989f464c52Smaya    EndGroup();
23999f464c52Smaya    return value_changed;
24009f464c52Smaya}
24019f464c52Smaya
24029f464c52Smayabool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
24039f464c52Smaya{
24049f464c52Smaya    return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
24059f464c52Smaya}
24069f464c52Smaya
24079f464c52Smayabool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
24089f464c52Smaya{
24099f464c52Smaya    return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
24109f464c52Smaya}
24119f464c52Smaya
24129f464c52Smayabool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
24139f464c52Smaya{
24149f464c52Smaya    return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
24159f464c52Smaya}
24169f464c52Smaya
24179f464c52Smayabool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
24189f464c52Smaya{
24199f464c52Smaya    return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
24209f464c52Smaya}
24219f464c52Smaya
24229f464c52Smayabool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format)
24239f464c52Smaya{
24249f464c52Smaya    if (format == NULL)
24259f464c52Smaya        format = "%.0f deg";
24269f464c52Smaya    float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
24279f464c52Smaya    bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, 1.0f);
24289f464c52Smaya    *v_rad = v_deg * (2*IM_PI) / 360.0f;
24299f464c52Smaya    return value_changed;
24309f464c52Smaya}
24319f464c52Smaya
24329f464c52Smayabool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
24339f464c52Smaya{
24349f464c52Smaya    return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
24359f464c52Smaya}
24369f464c52Smaya
24379f464c52Smayabool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
24389f464c52Smaya{
24399f464c52Smaya    return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
24409f464c52Smaya}
24419f464c52Smaya
24429f464c52Smayabool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
24439f464c52Smaya{
24449f464c52Smaya    return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
24459f464c52Smaya}
24469f464c52Smaya
24479f464c52Smayabool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
24489f464c52Smaya{
24499f464c52Smaya    return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
24509f464c52Smaya}
24519f464c52Smaya
24529f464c52Smayabool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
24539f464c52Smaya{
24549f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
24559f464c52Smaya    if (window->SkipItems)
24569f464c52Smaya        return false;
24579f464c52Smaya
24589f464c52Smaya    ImGuiContext& g = *GImGui;
24599f464c52Smaya    const ImGuiStyle& style = g.Style;
24609f464c52Smaya    const ImGuiID id = window->GetID(label);
24619f464c52Smaya
24629f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
24639f464c52Smaya    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
24649f464c52Smaya    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));
24659f464c52Smaya
24669f464c52Smaya    ItemSize(bb, style.FramePadding.y);
24679f464c52Smaya    if (!ItemAdd(frame_bb, id))
24689f464c52Smaya        return false;
24699f464c52Smaya
24709f464c52Smaya    // Default format string when passing NULL
24719f464c52Smaya    // Patch old "%.0f" format string to use "%d", read function comments for more details.
24729f464c52Smaya    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
24739f464c52Smaya    if (format == NULL)
24749f464c52Smaya        format = GDataTypeInfo[data_type].PrintFmt;
24759f464c52Smaya    else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
24769f464c52Smaya        format = PatchFormatStringFloatToInt(format);
24779f464c52Smaya
24789f464c52Smaya    const bool hovered = ItemHoverable(frame_bb, id);
24799f464c52Smaya    if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
24809f464c52Smaya    {
24819f464c52Smaya        SetActiveID(id, window);
24829f464c52Smaya        SetFocusID(id, window);
24839f464c52Smaya        FocusWindow(window);
24849f464c52Smaya        g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
24859f464c52Smaya    }
24869f464c52Smaya
24879f464c52Smaya    // Draw frame
24889f464c52Smaya    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
24899f464c52Smaya    RenderNavHighlight(frame_bb, id);
24909f464c52Smaya    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
24919f464c52Smaya
24929f464c52Smaya    // Slider behavior
24939f464c52Smaya    ImRect grab_bb;
24949f464c52Smaya    const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);
24959f464c52Smaya    if (value_changed)
24969f464c52Smaya        MarkItemEdited(id);
24979f464c52Smaya
24989f464c52Smaya    // Render grab
24999f464c52Smaya    window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
25009f464c52Smaya
25019f464c52Smaya    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
25029f464c52Smaya    // For the vertical slider we allow centered text to overlap the frame padding
25039f464c52Smaya    char value_buf[64];
25049f464c52Smaya    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
25059f464c52Smaya    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));
25069f464c52Smaya    if (label_size.x > 0.0f)
25079f464c52Smaya        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
25089f464c52Smaya
25099f464c52Smaya    return value_changed;
25109f464c52Smaya}
25119f464c52Smaya
25129f464c52Smayabool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
25139f464c52Smaya{
25149f464c52Smaya    return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
25159f464c52Smaya}
25169f464c52Smaya
25179f464c52Smayabool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
25189f464c52Smaya{
25199f464c52Smaya    return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
25209f464c52Smaya}
25219f464c52Smaya
25229f464c52Smaya//-------------------------------------------------------------------------
25239f464c52Smaya// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
25249f464c52Smaya//-------------------------------------------------------------------------
25259f464c52Smaya// - ImParseFormatFindStart() [Internal]
25269f464c52Smaya// - ImParseFormatFindEnd() [Internal]
25279f464c52Smaya// - ImParseFormatTrimDecorations() [Internal]
25289f464c52Smaya// - ImParseFormatPrecision() [Internal]
25299f464c52Smaya// - InputScalarAsWidgetReplacement() [Internal]
25309f464c52Smaya// - InputScalar()
25319f464c52Smaya// - InputScalarN()
25329f464c52Smaya// - InputFloat()
25339f464c52Smaya// - InputFloat2()
25349f464c52Smaya// - InputFloat3()
25359f464c52Smaya// - InputFloat4()
25369f464c52Smaya// - InputInt()
25379f464c52Smaya// - InputInt2()
25389f464c52Smaya// - InputInt3()
25399f464c52Smaya// - InputInt4()
25409f464c52Smaya// - InputDouble()
25419f464c52Smaya//-------------------------------------------------------------------------
25429f464c52Smaya
25439f464c52Smaya// We don't use strchr() because our strings are usually very short and often start with '%'
25449f464c52Smayaconst char* ImParseFormatFindStart(const char* fmt)
25459f464c52Smaya{
25469f464c52Smaya    while (char c = fmt[0])
25479f464c52Smaya    {
25489f464c52Smaya        if (c == '%' && fmt[1] != '%')
25499f464c52Smaya            return fmt;
25509f464c52Smaya        else if (c == '%')
25519f464c52Smaya            fmt++;
25529f464c52Smaya        fmt++;
25539f464c52Smaya    }
25549f464c52Smaya    return fmt;
25559f464c52Smaya}
25569f464c52Smaya
25579f464c52Smayaconst char* ImParseFormatFindEnd(const char* fmt)
25589f464c52Smaya{
25599f464c52Smaya    // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
25609f464c52Smaya    if (fmt[0] != '%')
25619f464c52Smaya        return fmt;
25629f464c52Smaya    const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
25639f464c52Smaya    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
25649f464c52Smaya    for (char c; (c = *fmt) != 0; fmt++)
25659f464c52Smaya    {
25669f464c52Smaya        if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
25679f464c52Smaya            return fmt + 1;
25689f464c52Smaya        if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
25699f464c52Smaya            return fmt + 1;
25709f464c52Smaya    }
25719f464c52Smaya    return fmt;
25729f464c52Smaya}
25739f464c52Smaya
25749f464c52Smaya// Extract the format out of a format string with leading or trailing decorations
25759f464c52Smaya//  fmt = "blah blah"  -> return fmt
25769f464c52Smaya//  fmt = "%.3f"       -> return fmt
25779f464c52Smaya//  fmt = "hello %.3f" -> return fmt + 6
25789f464c52Smaya//  fmt = "%.3f hello" -> return buf written with "%.3f"
25799f464c52Smayaconst char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
25809f464c52Smaya{
25819f464c52Smaya    const char* fmt_start = ImParseFormatFindStart(fmt);
25829f464c52Smaya    if (fmt_start[0] != '%')
25839f464c52Smaya        return fmt;
25849f464c52Smaya    const char* fmt_end = ImParseFormatFindEnd(fmt_start);
25859f464c52Smaya    if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
25869f464c52Smaya        return fmt_start;
25879f464c52Smaya    ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
25889f464c52Smaya    return buf;
25899f464c52Smaya}
25909f464c52Smaya
25919f464c52Smaya// Parse display precision back from the display format string
25929f464c52Smaya// 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.
25939f464c52Smayaint ImParseFormatPrecision(const char* fmt, int default_precision)
25949f464c52Smaya{
25959f464c52Smaya    fmt = ImParseFormatFindStart(fmt);
25969f464c52Smaya    if (fmt[0] != '%')
25979f464c52Smaya        return default_precision;
25989f464c52Smaya    fmt++;
25999f464c52Smaya    while (*fmt >= '0' && *fmt <= '9')
26009f464c52Smaya        fmt++;
26019f464c52Smaya    int precision = INT_MAX;
26029f464c52Smaya    if (*fmt == '.')
26039f464c52Smaya    {
26049f464c52Smaya        fmt = ImAtoi<int>(fmt + 1, &precision);
26059f464c52Smaya        if (precision < 0 || precision > 99)
26069f464c52Smaya            precision = default_precision;
26079f464c52Smaya    }
26089f464c52Smaya    if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
26099f464c52Smaya        precision = -1;
26109f464c52Smaya    if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
26119f464c52Smaya        precision = -1;
26129f464c52Smaya    return (precision == INT_MAX) ? default_precision : precision;
26139f464c52Smaya}
26149f464c52Smaya
26159f464c52Smaya// Create text input in place of an active drag/slider (used when doing a CTRL+Click on drag/slider widgets)
26169f464c52Smaya// FIXME: Facilitate using this in variety of other situations.
26179f464c52Smayabool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)
26189f464c52Smaya{
26199f464c52Smaya    ImGuiContext& g = *GImGui;
26209f464c52Smaya
26219f464c52Smaya    // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id.
26229f464c52Smaya    // We clear ActiveID on the first frame to allow the InputText() taking it back.
26239f464c52Smaya    if (g.ScalarAsInputTextId == 0)
26249f464c52Smaya        ClearActiveID();
26259f464c52Smaya
26269f464c52Smaya    char fmt_buf[32];
26279f464c52Smaya    char data_buf[32];
26289f464c52Smaya    format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
26299f464c52Smaya    DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);
26309f464c52Smaya    ImStrTrimBlanks(data_buf);
26319f464c52Smaya    ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
26329f464c52Smaya    bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);
26339f464c52Smaya    if (g.ScalarAsInputTextId == 0)
26349f464c52Smaya    {
26359f464c52Smaya        // First frame we started displaying the InputText widget, we expect it to take the active id.
26369f464c52Smaya        IM_ASSERT(g.ActiveId == id);
26379f464c52Smaya        g.ScalarAsInputTextId = g.ActiveId;
26389f464c52Smaya    }
26399f464c52Smaya    if (value_changed)
26409f464c52Smaya        return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);
26419f464c52Smaya    return false;
26429f464c52Smaya}
26439f464c52Smaya
26449f464c52Smayabool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags flags)
26459f464c52Smaya{
26469f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
26479f464c52Smaya    if (window->SkipItems)
26489f464c52Smaya        return false;
26499f464c52Smaya
26509f464c52Smaya    ImGuiContext& g = *GImGui;
26519f464c52Smaya    const ImGuiStyle& style = g.Style;
26529f464c52Smaya
26539f464c52Smaya    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
26549f464c52Smaya    if (format == NULL)
26559f464c52Smaya        format = GDataTypeInfo[data_type].PrintFmt;
26569f464c52Smaya
26579f464c52Smaya    char buf[64];
26589f464c52Smaya    DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
26599f464c52Smaya
26609f464c52Smaya    bool value_changed = false;
26619f464c52Smaya    if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
26629f464c52Smaya        flags |= ImGuiInputTextFlags_CharsDecimal;
26639f464c52Smaya    flags |= ImGuiInputTextFlags_AutoSelectAll;
26649f464c52Smaya
26659f464c52Smaya    if (step != NULL)
26669f464c52Smaya    {
26679f464c52Smaya        const float button_size = GetFrameHeight();
26689f464c52Smaya
26699f464c52Smaya        BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
26709f464c52Smaya        PushID(label);
26719f464c52Smaya        PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
26729f464c52Smaya        if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
26739f464c52Smaya            value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
26749f464c52Smaya        PopItemWidth();
26759f464c52Smaya
26769f464c52Smaya        // Step buttons
26779f464c52Smaya        ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
26789f464c52Smaya        if (flags & ImGuiInputTextFlags_ReadOnly)
26799f464c52Smaya            button_flags |= ImGuiButtonFlags_Disabled;
26809f464c52Smaya        SameLine(0, style.ItemInnerSpacing.x);
26819f464c52Smaya        if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
26829f464c52Smaya        {
26839f464c52Smaya            DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
26849f464c52Smaya            value_changed = true;
26859f464c52Smaya        }
26869f464c52Smaya        SameLine(0, style.ItemInnerSpacing.x);
26879f464c52Smaya        if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
26889f464c52Smaya        {
26899f464c52Smaya            DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
26909f464c52Smaya            value_changed = true;
26919f464c52Smaya        }
26929f464c52Smaya        SameLine(0, style.ItemInnerSpacing.x);
26939f464c52Smaya        TextUnformatted(label, FindRenderedTextEnd(label));
26949f464c52Smaya
26959f464c52Smaya        PopID();
26969f464c52Smaya        EndGroup();
26979f464c52Smaya    }
26989f464c52Smaya    else
26999f464c52Smaya    {
27009f464c52Smaya        if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
27019f464c52Smaya            value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
27029f464c52Smaya    }
27039f464c52Smaya
27049f464c52Smaya    return value_changed;
27059f464c52Smaya}
27069f464c52Smaya
27079f464c52Smayabool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags flags)
27089f464c52Smaya{
27099f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
27109f464c52Smaya    if (window->SkipItems)
27119f464c52Smaya        return false;
27129f464c52Smaya
27139f464c52Smaya    ImGuiContext& g = *GImGui;
27149f464c52Smaya    bool value_changed = false;
27159f464c52Smaya    BeginGroup();
27169f464c52Smaya    PushID(label);
27179f464c52Smaya    PushMultiItemsWidths(components);
27189f464c52Smaya    size_t type_size = GDataTypeInfo[data_type].Size;
27199f464c52Smaya    for (int i = 0; i < components; i++)
27209f464c52Smaya    {
27219f464c52Smaya        PushID(i);
27229f464c52Smaya        value_changed |= InputScalar("", data_type, v, step, step_fast, format, flags);
27239f464c52Smaya        SameLine(0, g.Style.ItemInnerSpacing.x);
27249f464c52Smaya        PopID();
27259f464c52Smaya        PopItemWidth();
27269f464c52Smaya        v = (void*)((char*)v + type_size);
27279f464c52Smaya    }
27289f464c52Smaya    PopID();
27299f464c52Smaya
27309f464c52Smaya    TextUnformatted(label, FindRenderedTextEnd(label));
27319f464c52Smaya    EndGroup();
27329f464c52Smaya    return value_changed;
27339f464c52Smaya}
27349f464c52Smaya
27359f464c52Smayabool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
27369f464c52Smaya{
27379f464c52Smaya    flags |= ImGuiInputTextFlags_CharsScientific;
27389f464c52Smaya    return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, flags);
27399f464c52Smaya}
27409f464c52Smaya
27419f464c52Smayabool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
27429f464c52Smaya{
27439f464c52Smaya    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
27449f464c52Smaya}
27459f464c52Smaya
27469f464c52Smayabool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
27479f464c52Smaya{
27489f464c52Smaya    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
27499f464c52Smaya}
27509f464c52Smaya
27519f464c52Smayabool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
27529f464c52Smaya{
27539f464c52Smaya    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
27549f464c52Smaya}
27559f464c52Smaya
27569f464c52Smaya// Prefer using "const char* format" directly, which is more flexible and consistent with other API.
27579f464c52Smaya#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
27589f464c52Smayabool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags flags)
27599f464c52Smaya{
27609f464c52Smaya    char format[16] = "%f";
27619f464c52Smaya    if (decimal_precision >= 0)
27629f464c52Smaya        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
27639f464c52Smaya    return InputFloat(label, v, step, step_fast, format, flags);
27649f464c52Smaya}
27659f464c52Smaya
27669f464c52Smayabool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags flags)
27679f464c52Smaya{
27689f464c52Smaya    char format[16] = "%f";
27699f464c52Smaya    if (decimal_precision >= 0)
27709f464c52Smaya        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
27719f464c52Smaya    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
27729f464c52Smaya}
27739f464c52Smaya
27749f464c52Smayabool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags flags)
27759f464c52Smaya{
27769f464c52Smaya    char format[16] = "%f";
27779f464c52Smaya    if (decimal_precision >= 0)
27789f464c52Smaya        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
27799f464c52Smaya    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
27809f464c52Smaya}
27819f464c52Smaya
27829f464c52Smayabool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags flags)
27839f464c52Smaya{
27849f464c52Smaya    char format[16] = "%f";
27859f464c52Smaya    if (decimal_precision >= 0)
27869f464c52Smaya        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
27879f464c52Smaya    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
27889f464c52Smaya}
27899f464c52Smaya#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
27909f464c52Smaya
27919f464c52Smayabool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
27929f464c52Smaya{
27939f464c52Smaya    // 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.
27949f464c52Smaya    const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
27959f464c52Smaya    return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, flags);
27969f464c52Smaya}
27979f464c52Smaya
27989f464c52Smayabool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
27999f464c52Smaya{
28009f464c52Smaya    return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
28019f464c52Smaya}
28029f464c52Smaya
28039f464c52Smayabool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
28049f464c52Smaya{
28059f464c52Smaya    return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
28069f464c52Smaya}
28079f464c52Smaya
28089f464c52Smayabool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
28099f464c52Smaya{
28109f464c52Smaya    return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
28119f464c52Smaya}
28129f464c52Smaya
28139f464c52Smayabool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
28149f464c52Smaya{
28159f464c52Smaya    flags |= ImGuiInputTextFlags_CharsScientific;
28169f464c52Smaya    return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, flags);
28179f464c52Smaya}
28189f464c52Smaya
28199f464c52Smaya//-------------------------------------------------------------------------
28209f464c52Smaya// [SECTION] Widgets: InputText, InputTextMultiline
28219f464c52Smaya//-------------------------------------------------------------------------
28229f464c52Smaya// - InputText()
28239f464c52Smaya// - InputTextMultiline()
28249f464c52Smaya// - InputTextEx() [Internal]
28259f464c52Smaya//-------------------------------------------------------------------------
28269f464c52Smaya
28279f464c52Smayabool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
28289f464c52Smaya{
28299f464c52Smaya    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
28309f464c52Smaya    return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
28319f464c52Smaya}
28329f464c52Smaya
28339f464c52Smayabool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
28349f464c52Smaya{
28359f464c52Smaya    return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
28369f464c52Smaya}
28379f464c52Smaya
28389f464c52Smayastatic int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
28399f464c52Smaya{
28409f464c52Smaya    int line_count = 0;
28419f464c52Smaya    const char* s = text_begin;
28429f464c52Smaya    while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
28439f464c52Smaya        if (c == '\n')
28449f464c52Smaya            line_count++;
28459f464c52Smaya    s--;
28469f464c52Smaya    if (s[0] != '\n' && s[0] != '\r')
28479f464c52Smaya        line_count++;
28489f464c52Smaya    *out_text_end = s;
28499f464c52Smaya    return line_count;
28509f464c52Smaya}
28519f464c52Smaya
28529f464c52Smayastatic ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
28539f464c52Smaya{
28549f464c52Smaya    ImGuiContext& g = *GImGui;
28559f464c52Smaya    ImFont* font = g.Font;
28569f464c52Smaya    const float line_height = g.FontSize;
28579f464c52Smaya    const float scale = line_height / font->FontSize;
28589f464c52Smaya
28599f464c52Smaya    ImVec2 text_size = ImVec2(0,0);
28609f464c52Smaya    float line_width = 0.0f;
28619f464c52Smaya
28629f464c52Smaya    const ImWchar* s = text_begin;
28639f464c52Smaya    while (s < text_end)
28649f464c52Smaya    {
28659f464c52Smaya        unsigned int c = (unsigned int)(*s++);
28669f464c52Smaya        if (c == '\n')
28679f464c52Smaya        {
28689f464c52Smaya            text_size.x = ImMax(text_size.x, line_width);
28699f464c52Smaya            text_size.y += line_height;
28709f464c52Smaya            line_width = 0.0f;
28719f464c52Smaya            if (stop_on_new_line)
28729f464c52Smaya                break;
28739f464c52Smaya            continue;
28749f464c52Smaya        }
28759f464c52Smaya        if (c == '\r')
28769f464c52Smaya            continue;
28779f464c52Smaya
28789f464c52Smaya        const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
28799f464c52Smaya        line_width += char_width;
28809f464c52Smaya    }
28819f464c52Smaya
28829f464c52Smaya    if (text_size.x < line_width)
28839f464c52Smaya        text_size.x = line_width;
28849f464c52Smaya
28859f464c52Smaya    if (out_offset)
28869f464c52Smaya        *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
28879f464c52Smaya
28889f464c52Smaya    if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
28899f464c52Smaya        text_size.y += line_height;
28909f464c52Smaya
28919f464c52Smaya    if (remaining)
28929f464c52Smaya        *remaining = s;
28939f464c52Smaya
28949f464c52Smaya    return text_size;
28959f464c52Smaya}
28969f464c52Smaya
28979f464c52Smaya// 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)
28989f464c52Smayanamespace ImGuiStb
28999f464c52Smaya{
29009f464c52Smaya
29019f464c52Smayastatic int     STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj)                             { return obj->CurLenW; }
29029f464c52Smayastatic ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx)                      { return obj->TextW[idx]; }
29039f464c52Smayastatic 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; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
29049f464c52Smayastatic int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x10000 ? 0 : key; }
29059f464c52Smayastatic ImWchar STB_TEXTEDIT_NEWLINE = '\n';
29069f464c52Smayastatic void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
29079f464c52Smaya{
29089f464c52Smaya    const ImWchar* text = obj->TextW.Data;
29099f464c52Smaya    const ImWchar* text_remaining = NULL;
29109f464c52Smaya    const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
29119f464c52Smaya    r->x0 = 0.0f;
29129f464c52Smaya    r->x1 = size.x;
29139f464c52Smaya    r->baseline_y_delta = size.y;
29149f464c52Smaya    r->ymin = 0.0f;
29159f464c52Smaya    r->ymax = size.y;
29169f464c52Smaya    r->num_chars = (int)(text_remaining - (text + line_start_idx));
29179f464c52Smaya}
29189f464c52Smaya
29199f464c52Smayastatic bool is_separator(unsigned int c)                                        { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
29209f464c52Smayastatic 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; }
29219f464c52Smayastatic 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; }
29229f464c52Smaya#ifdef __APPLE__    // FIXME: Move setting to IO structure
29239f464c52Smayastatic 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; }
29249f464c52Smayastatic 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; }
29259f464c52Smaya#else
29269f464c52Smayastatic 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; }
29279f464c52Smaya#endif
29289f464c52Smaya#define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL    // They need to be #define for stb_textedit.h
29299f464c52Smaya#define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
29309f464c52Smaya
29319f464c52Smayastatic void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
29329f464c52Smaya{
29339f464c52Smaya    ImWchar* dst = obj->TextW.Data + pos;
29349f464c52Smaya
29359f464c52Smaya    // We maintain our buffer length in both UTF-8 and wchar formats
29369f464c52Smaya    obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
29379f464c52Smaya    obj->CurLenW -= n;
29389f464c52Smaya
29399f464c52Smaya    // Offset remaining text (FIXME-OPT: Use memmove)
29409f464c52Smaya    const ImWchar* src = obj->TextW.Data + pos + n;
29419f464c52Smaya    while (ImWchar c = *src++)
29429f464c52Smaya        *dst++ = c;
29439f464c52Smaya    *dst = '\0';
29449f464c52Smaya}
29459f464c52Smaya
29469f464c52Smayastatic bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
29479f464c52Smaya{
29489f464c52Smaya    const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
29499f464c52Smaya    const int text_len = obj->CurLenW;
29509f464c52Smaya    IM_ASSERT(pos <= text_len);
29519f464c52Smaya
29529f464c52Smaya    const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
29539f464c52Smaya    if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
29549f464c52Smaya        return false;
29559f464c52Smaya
29569f464c52Smaya    // Grow internal buffer if needed
29579f464c52Smaya    if (new_text_len + text_len + 1 > obj->TextW.Size)
29589f464c52Smaya    {
29599f464c52Smaya        if (!is_resizable)
29609f464c52Smaya            return false;
29619f464c52Smaya        IM_ASSERT(text_len < obj->TextW.Size);
29629f464c52Smaya        obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
29639f464c52Smaya    }
29649f464c52Smaya
29659f464c52Smaya    ImWchar* text = obj->TextW.Data;
29669f464c52Smaya    if (pos != text_len)
29679f464c52Smaya        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
29689f464c52Smaya    memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
29699f464c52Smaya
29709f464c52Smaya    obj->CurLenW += new_text_len;
29719f464c52Smaya    obj->CurLenA += new_text_len_utf8;
29729f464c52Smaya    obj->TextW[obj->CurLenW] = '\0';
29739f464c52Smaya
29749f464c52Smaya    return true;
29759f464c52Smaya}
29769f464c52Smaya
29779f464c52Smaya// 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)
29789f464c52Smaya#define STB_TEXTEDIT_K_LEFT         0x10000 // keyboard input to move cursor left
29799f464c52Smaya#define STB_TEXTEDIT_K_RIGHT        0x10001 // keyboard input to move cursor right
29809f464c52Smaya#define STB_TEXTEDIT_K_UP           0x10002 // keyboard input to move cursor up
29819f464c52Smaya#define STB_TEXTEDIT_K_DOWN         0x10003 // keyboard input to move cursor down
29829f464c52Smaya#define STB_TEXTEDIT_K_LINESTART    0x10004 // keyboard input to move cursor to start of line
29839f464c52Smaya#define STB_TEXTEDIT_K_LINEEND      0x10005 // keyboard input to move cursor to end of line
29849f464c52Smaya#define STB_TEXTEDIT_K_TEXTSTART    0x10006 // keyboard input to move cursor to start of text
29859f464c52Smaya#define STB_TEXTEDIT_K_TEXTEND      0x10007 // keyboard input to move cursor to end of text
29869f464c52Smaya#define STB_TEXTEDIT_K_DELETE       0x10008 // keyboard input to delete selection or character under cursor
29879f464c52Smaya#define STB_TEXTEDIT_K_BACKSPACE    0x10009 // keyboard input to delete selection or character left of cursor
29889f464c52Smaya#define STB_TEXTEDIT_K_UNDO         0x1000A // keyboard input to perform undo
29899f464c52Smaya#define STB_TEXTEDIT_K_REDO         0x1000B // keyboard input to perform redo
29909f464c52Smaya#define STB_TEXTEDIT_K_WORDLEFT     0x1000C // keyboard input to move cursor left one word
29919f464c52Smaya#define STB_TEXTEDIT_K_WORDRIGHT    0x1000D // keyboard input to move cursor right one word
29929f464c52Smaya#define STB_TEXTEDIT_K_SHIFT        0x20000
29939f464c52Smaya
29949f464c52Smaya#define STB_TEXTEDIT_IMPLEMENTATION
29959f464c52Smaya#include "imstb_textedit.h"
29969f464c52Smaya
29979f464c52Smaya}
29989f464c52Smaya
29999f464c52Smayavoid ImGuiInputTextState::OnKeyPressed(int key)
30009f464c52Smaya{
30019f464c52Smaya    stb_textedit_key(this, &StbState, key);
30029f464c52Smaya    CursorFollow = true;
30039f464c52Smaya    CursorAnimReset();
30049f464c52Smaya}
30059f464c52Smaya
30069f464c52SmayaImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
30079f464c52Smaya{
30089f464c52Smaya    memset(this, 0, sizeof(*this));
30099f464c52Smaya}
30109f464c52Smaya
30119f464c52Smaya// Public API to manipulate UTF-8 text
30129f464c52Smaya// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
30139f464c52Smaya// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
30149f464c52Smayavoid ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
30159f464c52Smaya{
30169f464c52Smaya    IM_ASSERT(pos + bytes_count <= BufTextLen);
30179f464c52Smaya    char* dst = Buf + pos;
30189f464c52Smaya    const char* src = Buf + pos + bytes_count;
30199f464c52Smaya    while (char c = *src++)
30209f464c52Smaya        *dst++ = c;
30219f464c52Smaya    *dst = '\0';
30229f464c52Smaya
30239f464c52Smaya    if (CursorPos + bytes_count >= pos)
30249f464c52Smaya        CursorPos -= bytes_count;
30259f464c52Smaya    else if (CursorPos >= pos)
30269f464c52Smaya        CursorPos = pos;
30279f464c52Smaya    SelectionStart = SelectionEnd = CursorPos;
30289f464c52Smaya    BufDirty = true;
30299f464c52Smaya    BufTextLen -= bytes_count;
30309f464c52Smaya}
30319f464c52Smaya
30329f464c52Smayavoid ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
30339f464c52Smaya{
30349f464c52Smaya    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
30359f464c52Smaya    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
30369f464c52Smaya    if (new_text_len + BufTextLen >= BufSize)
30379f464c52Smaya    {
30389f464c52Smaya        if (!is_resizable)
30399f464c52Smaya            return;
30409f464c52Smaya
30419f464c52Smaya        // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
30429f464c52Smaya        ImGuiContext& g = *GImGui;
30439f464c52Smaya        ImGuiInputTextState* edit_state = &g.InputTextState;
30449f464c52Smaya        IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
30459f464c52Smaya        IM_ASSERT(Buf == edit_state->TempBuffer.Data);
30469f464c52Smaya        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
30479f464c52Smaya        edit_state->TempBuffer.reserve(new_buf_size + 1);
30489f464c52Smaya        Buf = edit_state->TempBuffer.Data;
30499f464c52Smaya        BufSize = edit_state->BufCapacityA = new_buf_size;
30509f464c52Smaya    }
30519f464c52Smaya
30529f464c52Smaya    if (BufTextLen != pos)
30539f464c52Smaya        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
30549f464c52Smaya    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
30559f464c52Smaya    Buf[BufTextLen + new_text_len] = '\0';
30569f464c52Smaya
30579f464c52Smaya    if (CursorPos >= pos)
30589f464c52Smaya        CursorPos += new_text_len;
30599f464c52Smaya    SelectionStart = SelectionEnd = CursorPos;
30609f464c52Smaya    BufDirty = true;
30619f464c52Smaya    BufTextLen += new_text_len;
30629f464c52Smaya}
30639f464c52Smaya
30649f464c52Smaya// Return false to discard a character.
30659f464c52Smayastatic bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
30669f464c52Smaya{
30679f464c52Smaya    unsigned int c = *p_char;
30689f464c52Smaya
30699f464c52Smaya    if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
30709f464c52Smaya    {
30719f464c52Smaya        bool pass = false;
30729f464c52Smaya        pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
30739f464c52Smaya        pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
30749f464c52Smaya        if (!pass)
30759f464c52Smaya            return false;
30769f464c52Smaya    }
30779f464c52Smaya
30789f464c52Smaya    if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
30799f464c52Smaya        return false;
30809f464c52Smaya
30819f464c52Smaya    if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
30829f464c52Smaya    {
30839f464c52Smaya        if (flags & ImGuiInputTextFlags_CharsDecimal)
30849f464c52Smaya            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
30859f464c52Smaya                return false;
30869f464c52Smaya
30879f464c52Smaya        if (flags & ImGuiInputTextFlags_CharsScientific)
30889f464c52Smaya            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
30899f464c52Smaya                return false;
30909f464c52Smaya
30919f464c52Smaya        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
30929f464c52Smaya            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
30939f464c52Smaya                return false;
30949f464c52Smaya
30959f464c52Smaya        if (flags & ImGuiInputTextFlags_CharsUppercase)
30969f464c52Smaya            if (c >= 'a' && c <= 'z')
30979f464c52Smaya                *p_char = (c += (unsigned int)('A'-'a'));
30989f464c52Smaya
30999f464c52Smaya        if (flags & ImGuiInputTextFlags_CharsNoBlank)
31009f464c52Smaya            if (ImCharIsBlankW(c))
31019f464c52Smaya                return false;
31029f464c52Smaya    }
31039f464c52Smaya
31049f464c52Smaya    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
31059f464c52Smaya    {
31069f464c52Smaya        ImGuiInputTextCallbackData callback_data;
31079f464c52Smaya        memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
31089f464c52Smaya        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
31099f464c52Smaya        callback_data.EventChar = (ImWchar)c;
31109f464c52Smaya        callback_data.Flags = flags;
31119f464c52Smaya        callback_data.UserData = user_data;
31129f464c52Smaya        if (callback(&callback_data) != 0)
31139f464c52Smaya            return false;
31149f464c52Smaya        *p_char = callback_data.EventChar;
31159f464c52Smaya        if (!callback_data.EventChar)
31169f464c52Smaya            return false;
31179f464c52Smaya    }
31189f464c52Smaya
31199f464c52Smaya    return true;
31209f464c52Smaya}
31219f464c52Smaya
31229f464c52Smaya// Edit a string of text
31239f464c52Smaya// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
31249f464c52Smaya//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
31259f464c52Smaya//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
31269f464c52Smaya// - 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.
31279f464c52Smaya// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
31289f464c52Smaya// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
31299f464c52Smayabool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
31309f464c52Smaya{
31319f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
31329f464c52Smaya    if (window->SkipItems)
31339f464c52Smaya        return false;
31349f464c52Smaya
31359f464c52Smaya    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
31369f464c52Smaya    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
31379f464c52Smaya
31389f464c52Smaya    ImGuiContext& g = *GImGui;
31399f464c52Smaya    ImGuiIO& io = g.IO;
31409f464c52Smaya    const ImGuiStyle& style = g.Style;
31419f464c52Smaya
31429f464c52Smaya    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
31439f464c52Smaya    const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
31449f464c52Smaya    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
31459f464c52Smaya    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
31469f464c52Smaya    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
31479f464c52Smaya    if (is_resizable)
31489f464c52Smaya        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
31499f464c52Smaya
31509f464c52Smaya    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
31519f464c52Smaya        BeginGroup();
31529f464c52Smaya    const ImGuiID id = window->GetID(label);
31539f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
31549f464c52Smaya    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
31559f464c52Smaya    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
31569f464c52Smaya    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));
31579f464c52Smaya
31589f464c52Smaya    ImGuiWindow* draw_window = window;
31599f464c52Smaya    if (is_multiline)
31609f464c52Smaya    {
31619f464c52Smaya        if (!ItemAdd(total_bb, id, &frame_bb))
31629f464c52Smaya        {
31639f464c52Smaya            ItemSize(total_bb, style.FramePadding.y);
31649f464c52Smaya            EndGroup();
31659f464c52Smaya            return false;
31669f464c52Smaya        }
31679f464c52Smaya        if (!BeginChildFrame(id, frame_bb.GetSize()))
31689f464c52Smaya        {
31699f464c52Smaya            EndChildFrame();
31709f464c52Smaya            EndGroup();
31719f464c52Smaya            return false;
31729f464c52Smaya        }
31739f464c52Smaya        draw_window = GetCurrentWindow();
31749f464c52Smaya        draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
31759f464c52Smaya        size.x -= draw_window->ScrollbarSizes.x;
31769f464c52Smaya    }
31779f464c52Smaya    else
31789f464c52Smaya    {
31799f464c52Smaya        ItemSize(total_bb, style.FramePadding.y);
31809f464c52Smaya        if (!ItemAdd(total_bb, id, &frame_bb))
31819f464c52Smaya            return false;
31829f464c52Smaya    }
31839f464c52Smaya    const bool hovered = ItemHoverable(frame_bb, id);
31849f464c52Smaya    if (hovered)
31859f464c52Smaya        g.MouseCursor = ImGuiMouseCursor_TextInput;
31869f464c52Smaya
31879f464c52Smaya    // Password pushes a temporary font with only a fallback glyph
31889f464c52Smaya    if (is_password)
31899f464c52Smaya    {
31909f464c52Smaya        const ImFontGlyph* glyph = g.Font->FindGlyph('*');
31919f464c52Smaya        ImFont* password_font = &g.InputTextPasswordFont;
31929f464c52Smaya        password_font->FontSize = g.Font->FontSize;
31939f464c52Smaya        password_font->Scale = g.Font->Scale;
31949f464c52Smaya        password_font->DisplayOffset = g.Font->DisplayOffset;
31959f464c52Smaya        password_font->Ascent = g.Font->Ascent;
31969f464c52Smaya        password_font->Descent = g.Font->Descent;
31979f464c52Smaya        password_font->ContainerAtlas = g.Font->ContainerAtlas;
31989f464c52Smaya        password_font->FallbackGlyph = glyph;
31999f464c52Smaya        password_font->FallbackAdvanceX = glyph->AdvanceX;
32009f464c52Smaya        IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
32019f464c52Smaya        PushFont(password_font);
32029f464c52Smaya    }
32039f464c52Smaya
32049f464c52Smaya    // NB: we are only allowed to access 'edit_state' if we are the active widget.
32059f464c52Smaya    ImGuiInputTextState& edit_state = g.InputTextState;
32069f464c52Smaya
32079f464c52Smaya    const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0);    // Using completion callback disable keyboard tabbing
32089f464c52Smaya    const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
32099f464c52Smaya    const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
32109f464c52Smaya
32119f464c52Smaya    const bool user_clicked = hovered && io.MouseClicked[0];
32129f464c52Smaya    const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
32139f464c52Smaya    const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
32149f464c52Smaya
32159f464c52Smaya    bool clear_active_id = false;
32169f464c52Smaya
32179f464c52Smaya    bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
32189f464c52Smaya    if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
32199f464c52Smaya    {
32209f464c52Smaya        if (g.ActiveId != id)
32219f464c52Smaya        {
32229f464c52Smaya            // Start edition
32239f464c52Smaya            // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
32249f464c52Smaya            // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
32259f464c52Smaya            const int prev_len_w = edit_state.CurLenW;
32269f464c52Smaya            const int init_buf_len = (int)strlen(buf);
32279f464c52Smaya            edit_state.TextW.resize(buf_size+1);             // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
32289f464c52Smaya            edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
32299f464c52Smaya            memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
32309f464c52Smaya            const char* buf_end = NULL;
32319f464c52Smaya            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
32329f464c52Smaya            edit_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.
32339f464c52Smaya            edit_state.CursorAnimReset();
32349f464c52Smaya
32359f464c52Smaya            // Preserve cursor position and undo/redo stack if we come back to same widget
32369f464c52Smaya            // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
32379f464c52Smaya            const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
32389f464c52Smaya            if (recycle_state)
32399f464c52Smaya            {
32409f464c52Smaya                // Recycle existing cursor/selection/undo stack but clamp position
32419f464c52Smaya                // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
32429f464c52Smaya                edit_state.CursorClamp();
32439f464c52Smaya            }
32449f464c52Smaya            else
32459f464c52Smaya            {
32469f464c52Smaya                edit_state.ID = id;
32479f464c52Smaya                edit_state.ScrollX = 0.0f;
32489f464c52Smaya                stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
32499f464c52Smaya                if (!is_multiline && focus_requested_by_code)
32509f464c52Smaya                    select_all = true;
32519f464c52Smaya            }
32529f464c52Smaya            if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
32539f464c52Smaya                edit_state.StbState.insert_mode = 1;
32549f464c52Smaya            if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
32559f464c52Smaya                select_all = true;
32569f464c52Smaya        }
32579f464c52Smaya        SetActiveID(id, window);
32589f464c52Smaya        SetFocusID(id, window);
32599f464c52Smaya        FocusWindow(window);
32609f464c52Smaya        g.ActiveIdBlockNavInputFlags = (1 << ImGuiNavInput_Cancel);
32619f464c52Smaya        if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
32629f464c52Smaya            g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
32639f464c52Smaya    }
32649f464c52Smaya    else if (io.MouseClicked[0])
32659f464c52Smaya    {
32669f464c52Smaya        // Release focus when we click outside
32679f464c52Smaya        clear_active_id = true;
32689f464c52Smaya    }
32699f464c52Smaya
32709f464c52Smaya    bool value_changed = false;
32719f464c52Smaya    bool enter_pressed = false;
32729f464c52Smaya    int backup_current_text_length = 0;
32739f464c52Smaya
32749f464c52Smaya    if (g.ActiveId == id)
32759f464c52Smaya    {
32769f464c52Smaya        if (!is_editable && !g.ActiveIdIsJustActivated)
32779f464c52Smaya        {
32789f464c52Smaya            // When read-only we always use the live data passed to the function
32799f464c52Smaya            edit_state.TextW.resize(buf_size+1);
32809f464c52Smaya            const char* buf_end = NULL;
32819f464c52Smaya            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
32829f464c52Smaya            edit_state.CurLenA = (int)(buf_end - buf);
32839f464c52Smaya            edit_state.CursorClamp();
32849f464c52Smaya        }
32859f464c52Smaya
32869f464c52Smaya        backup_current_text_length = edit_state.CurLenA;
32879f464c52Smaya        edit_state.BufCapacityA = buf_size;
32889f464c52Smaya        edit_state.UserFlags = flags;
32899f464c52Smaya        edit_state.UserCallback = callback;
32909f464c52Smaya        edit_state.UserCallbackData = callback_user_data;
32919f464c52Smaya
32929f464c52Smaya        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
32939f464c52Smaya        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
32949f464c52Smaya        g.ActiveIdAllowOverlap = !io.MouseDown[0];
32959f464c52Smaya        g.WantTextInputNextFrame = 1;
32969f464c52Smaya
32979f464c52Smaya        // Edit in progress
32989f464c52Smaya        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
32999f464c52Smaya        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
33009f464c52Smaya
33019f464c52Smaya        const bool is_osx = io.ConfigMacOSXBehaviors;
33029f464c52Smaya        if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
33039f464c52Smaya        {
33049f464c52Smaya            edit_state.SelectAll();
33059f464c52Smaya            edit_state.SelectedAllMouseLock = true;
33069f464c52Smaya        }
33079f464c52Smaya        else if (hovered && is_osx && io.MouseDoubleClicked[0])
33089f464c52Smaya        {
33099f464c52Smaya            // Double-click select a word only, OS X style (by simulating keystrokes)
33109f464c52Smaya            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
33119f464c52Smaya            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
33129f464c52Smaya        }
33139f464c52Smaya        else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
33149f464c52Smaya        {
33159f464c52Smaya            if (hovered)
33169f464c52Smaya            {
33179f464c52Smaya                stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
33189f464c52Smaya                edit_state.CursorAnimReset();
33199f464c52Smaya            }
33209f464c52Smaya        }
33219f464c52Smaya        else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
33229f464c52Smaya        {
33239f464c52Smaya            stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
33249f464c52Smaya            edit_state.CursorAnimReset();
33259f464c52Smaya            edit_state.CursorFollow = true;
33269f464c52Smaya        }
33279f464c52Smaya        if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
33289f464c52Smaya            edit_state.SelectedAllMouseLock = false;
33299f464c52Smaya
33309f464c52Smaya        if (io.InputQueueCharacters.Size > 0)
33319f464c52Smaya        {
33329f464c52Smaya            // Process text input (before we check for Return because using some IME will effectively send a Return?)
33339f464c52Smaya            // 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.
33349f464c52Smaya            bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
33359f464c52Smaya            if (!ignore_inputs && is_editable && !user_nav_input_start)
33369f464c52Smaya                for (int n = 0; n < io.InputQueueCharacters.Size; n++)
33379f464c52Smaya                {
33389f464c52Smaya                    // Insert character if they pass filtering
33399f464c52Smaya                    unsigned int c = (unsigned int)io.InputQueueCharacters[n];
33409f464c52Smaya                    if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
33419f464c52Smaya                        edit_state.OnKeyPressed((int)c);
33429f464c52Smaya                }
33439f464c52Smaya
33449f464c52Smaya            // Consume characters
33459f464c52Smaya            io.InputQueueCharacters.resize(0);
33469f464c52Smaya        }
33479f464c52Smaya    }
33489f464c52Smaya
33499f464c52Smaya    bool cancel_edit = false;
33509f464c52Smaya    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
33519f464c52Smaya    {
33529f464c52Smaya        // Handle key-presses
33539f464c52Smaya        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
33549f464c52Smaya        const bool is_osx = io.ConfigMacOSXBehaviors;
33559f464c52Smaya        const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
33569f464c52Smaya        const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
33579f464c52Smaya        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
33589f464c52Smaya        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
33599f464c52Smaya        const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
33609f464c52Smaya        const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
33619f464c52Smaya
33629f464c52Smaya        const bool is_cut   = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
33639f464c52Smaya        const bool is_copy  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only  && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
33649f464c52Smaya        const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
33659f464c52Smaya        const bool is_undo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
33669f464c52Smaya        const bool is_redo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
33679f464c52Smaya
33689f464c52Smaya        if (IsKeyPressedMap(ImGuiKey_LeftArrow))                        { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
33699f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_RightArrow))                  { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
33709f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
33719f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
33729f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_Home))                        { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
33739f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_End))                         { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
33749f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable)       { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
33759f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
33769f464c52Smaya        {
33779f464c52Smaya            if (!edit_state.HasSelection())
33789f464c52Smaya            {
33799f464c52Smaya                if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
33809f464c52Smaya                else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
33819f464c52Smaya            }
33829f464c52Smaya            edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
33839f464c52Smaya        }
33849f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_Enter))
33859f464c52Smaya        {
33869f464c52Smaya            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
33879f464c52Smaya            if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
33889f464c52Smaya            {
33899f464c52Smaya                enter_pressed = clear_active_id = true;
33909f464c52Smaya            }
33919f464c52Smaya            else if (is_editable)
33929f464c52Smaya            {
33939f464c52Smaya                unsigned int c = '\n'; // Insert new line
33949f464c52Smaya                if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
33959f464c52Smaya                    edit_state.OnKeyPressed((int)c);
33969f464c52Smaya            }
33979f464c52Smaya        }
33989f464c52Smaya        else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
33999f464c52Smaya        {
34009f464c52Smaya            unsigned int c = '\t'; // Insert TAB
34019f464c52Smaya            if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
34029f464c52Smaya                edit_state.OnKeyPressed((int)c);
34039f464c52Smaya        }
34049f464c52Smaya        else if (IsKeyPressedMap(ImGuiKey_Escape))
34059f464c52Smaya        {
34069f464c52Smaya            clear_active_id = cancel_edit = true;
34079f464c52Smaya        }
34089f464c52Smaya        else if (is_undo || is_redo)
34099f464c52Smaya        {
34109f464c52Smaya            edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
34119f464c52Smaya            edit_state.ClearSelection();
34129f464c52Smaya        }
34139f464c52Smaya        else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
34149f464c52Smaya        {
34159f464c52Smaya            edit_state.SelectAll();
34169f464c52Smaya            edit_state.CursorFollow = true;
34179f464c52Smaya        }
34189f464c52Smaya        else if (is_cut || is_copy)
34199f464c52Smaya        {
34209f464c52Smaya            // Cut, Copy
34219f464c52Smaya            if (io.SetClipboardTextFn)
34229f464c52Smaya            {
34239f464c52Smaya                const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
34249f464c52Smaya                const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
34259f464c52Smaya                edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
34269f464c52Smaya                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
34279f464c52Smaya                SetClipboardText(edit_state.TempBuffer.Data);
34289f464c52Smaya            }
34299f464c52Smaya            if (is_cut)
34309f464c52Smaya            {
34319f464c52Smaya                if (!edit_state.HasSelection())
34329f464c52Smaya                    edit_state.SelectAll();
34339f464c52Smaya                edit_state.CursorFollow = true;
34349f464c52Smaya                stb_textedit_cut(&edit_state, &edit_state.StbState);
34359f464c52Smaya            }
34369f464c52Smaya        }
34379f464c52Smaya        else if (is_paste)
34389f464c52Smaya        {
34399f464c52Smaya            if (const char* clipboard = GetClipboardText())
34409f464c52Smaya            {
34419f464c52Smaya                // Filter pasted buffer
34429f464c52Smaya                const int clipboard_len = (int)strlen(clipboard);
34439f464c52Smaya                ImWchar* clipboard_filtered = (ImWchar*)MemAlloc((clipboard_len+1) * sizeof(ImWchar));
34449f464c52Smaya                int clipboard_filtered_len = 0;
34459f464c52Smaya                for (const char* s = clipboard; *s; )
34469f464c52Smaya                {
34479f464c52Smaya                    unsigned int c;
34489f464c52Smaya                    s += ImTextCharFromUtf8(&c, s, NULL);
34499f464c52Smaya                    if (c == 0)
34509f464c52Smaya                        break;
34519f464c52Smaya                    if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
34529f464c52Smaya                        continue;
34539f464c52Smaya                    clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
34549f464c52Smaya                }
34559f464c52Smaya                clipboard_filtered[clipboard_filtered_len] = 0;
34569f464c52Smaya                if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
34579f464c52Smaya                {
34589f464c52Smaya                    stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
34599f464c52Smaya                    edit_state.CursorFollow = true;
34609f464c52Smaya                }
34619f464c52Smaya                MemFree(clipboard_filtered);
34629f464c52Smaya            }
34639f464c52Smaya        }
34649f464c52Smaya    }
34659f464c52Smaya
34669f464c52Smaya    if (g.ActiveId == id)
34679f464c52Smaya    {
34689f464c52Smaya        const char* apply_new_text = NULL;
34699f464c52Smaya        int apply_new_text_length = 0;
34709f464c52Smaya        if (cancel_edit)
34719f464c52Smaya        {
34729f464c52Smaya            // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
34739f464c52Smaya            if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
34749f464c52Smaya            {
34759f464c52Smaya                apply_new_text = edit_state.InitialText.Data;
34769f464c52Smaya                apply_new_text_length = edit_state.InitialText.Size - 1;
34779f464c52Smaya            }
34789f464c52Smaya        }
34799f464c52Smaya
34809f464c52Smaya        // 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.
34819f464c52Smaya        // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
34829f464c52Smaya        bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
34839f464c52Smaya        if (apply_edit_back_to_user_buffer)
34849f464c52Smaya        {
34859f464c52Smaya            // Apply new value immediately - copy modified buffer back
34869f464c52Smaya            // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
34879f464c52Smaya            // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
34889f464c52Smaya            // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
34899f464c52Smaya            if (is_editable)
34909f464c52Smaya            {
34919f464c52Smaya                edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
34929f464c52Smaya                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
34939f464c52Smaya            }
34949f464c52Smaya
34959f464c52Smaya            // User callback
34969f464c52Smaya            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
34979f464c52Smaya            {
34989f464c52Smaya                IM_ASSERT(callback != NULL);
34999f464c52Smaya
35009f464c52Smaya                // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
35019f464c52Smaya                ImGuiInputTextFlags event_flag = 0;
35029f464c52Smaya                ImGuiKey event_key = ImGuiKey_COUNT;
35039f464c52Smaya                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
35049f464c52Smaya                {
35059f464c52Smaya                    event_flag = ImGuiInputTextFlags_CallbackCompletion;
35069f464c52Smaya                    event_key = ImGuiKey_Tab;
35079f464c52Smaya                }
35089f464c52Smaya                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
35099f464c52Smaya                {
35109f464c52Smaya                    event_flag = ImGuiInputTextFlags_CallbackHistory;
35119f464c52Smaya                    event_key = ImGuiKey_UpArrow;
35129f464c52Smaya                }
35139f464c52Smaya                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
35149f464c52Smaya                {
35159f464c52Smaya                    event_flag = ImGuiInputTextFlags_CallbackHistory;
35169f464c52Smaya                    event_key = ImGuiKey_DownArrow;
35179f464c52Smaya                }
35189f464c52Smaya                else if (flags & ImGuiInputTextFlags_CallbackAlways)
35199f464c52Smaya                    event_flag = ImGuiInputTextFlags_CallbackAlways;
35209f464c52Smaya
35219f464c52Smaya                if (event_flag)
35229f464c52Smaya                {
35239f464c52Smaya                    ImGuiInputTextCallbackData callback_data;
35249f464c52Smaya                    memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
35259f464c52Smaya                    callback_data.EventFlag = event_flag;
35269f464c52Smaya                    callback_data.Flags = flags;
35279f464c52Smaya                    callback_data.UserData = callback_user_data;
35289f464c52Smaya
35299f464c52Smaya                    callback_data.EventKey = event_key;
35309f464c52Smaya                    callback_data.Buf = edit_state.TempBuffer.Data;
35319f464c52Smaya                    callback_data.BufTextLen = edit_state.CurLenA;
35329f464c52Smaya                    callback_data.BufSize = edit_state.BufCapacityA;
35339f464c52Smaya                    callback_data.BufDirty = false;
35349f464c52Smaya
35359f464c52Smaya                    // 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)
35369f464c52Smaya                    ImWchar* text = edit_state.TextW.Data;
35379f464c52Smaya                    const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
35389f464c52Smaya                    const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
35399f464c52Smaya                    const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
35409f464c52Smaya
35419f464c52Smaya                    // Call user code
35429f464c52Smaya                    callback(&callback_data);
35439f464c52Smaya
35449f464c52Smaya                    // Read back what user may have modified
35459f464c52Smaya                    IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data);  // Invalid to modify those fields
35469f464c52Smaya                    IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
35479f464c52Smaya                    IM_ASSERT(callback_data.Flags == flags);
35489f464c52Smaya                    if (callback_data.CursorPos != utf8_cursor_pos)            { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
35499f464c52Smaya                    if (callback_data.SelectionStart != utf8_selection_start)  { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
35509f464c52Smaya                    if (callback_data.SelectionEnd != utf8_selection_end)      { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
35519f464c52Smaya                    if (callback_data.BufDirty)
35529f464c52Smaya                    {
35539f464c52Smaya                        IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
35549f464c52Smaya                        if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
35559f464c52Smaya                            edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
35569f464c52Smaya                        edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
35579f464c52Smaya                        edit_state.CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
35589f464c52Smaya                        edit_state.CursorAnimReset();
35599f464c52Smaya                    }
35609f464c52Smaya                }
35619f464c52Smaya            }
35629f464c52Smaya
35639f464c52Smaya            // Will copy result string if modified
35649f464c52Smaya            if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
35659f464c52Smaya            {
35669f464c52Smaya                apply_new_text = edit_state.TempBuffer.Data;
35679f464c52Smaya                apply_new_text_length = edit_state.CurLenA;
35689f464c52Smaya            }
35699f464c52Smaya        }
35709f464c52Smaya
35719f464c52Smaya        // Copy result to user buffer
35729f464c52Smaya        if (apply_new_text)
35739f464c52Smaya        {
35749f464c52Smaya            IM_ASSERT(apply_new_text_length >= 0);
35759f464c52Smaya            if (backup_current_text_length != apply_new_text_length && is_resizable)
35769f464c52Smaya            {
35779f464c52Smaya                ImGuiInputTextCallbackData callback_data;
35789f464c52Smaya                callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
35799f464c52Smaya                callback_data.Flags = flags;
35809f464c52Smaya                callback_data.Buf = buf;
35819f464c52Smaya                callback_data.BufTextLen = apply_new_text_length;
35829f464c52Smaya                callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
35839f464c52Smaya                callback_data.UserData = callback_user_data;
35849f464c52Smaya                callback(&callback_data);
35859f464c52Smaya                buf = callback_data.Buf;
35869f464c52Smaya                buf_size = callback_data.BufSize;
35879f464c52Smaya                apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
35889f464c52Smaya                IM_ASSERT(apply_new_text_length <= buf_size);
35899f464c52Smaya            }
35909f464c52Smaya
35919f464c52Smaya            // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
35929f464c52Smaya            ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
35939f464c52Smaya            value_changed = true;
35949f464c52Smaya        }
35959f464c52Smaya
35969f464c52Smaya        // Clear temporary user storage
35979f464c52Smaya        edit_state.UserFlags = 0;
35989f464c52Smaya        edit_state.UserCallback = NULL;
35999f464c52Smaya        edit_state.UserCallbackData = NULL;
36009f464c52Smaya    }
36019f464c52Smaya
36029f464c52Smaya    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
36039f464c52Smaya    if (clear_active_id && g.ActiveId == id)
36049f464c52Smaya        ClearActiveID();
36059f464c52Smaya
36069f464c52Smaya    // Render
36079f464c52Smaya    // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
36089f464c52Smaya    const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
36099f464c52Smaya
36109f464c52Smaya    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
36119f464c52Smaya    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
36129f464c52Smaya    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
36139f464c52Smaya    const int buf_display_max_length = 2 * 1024 * 1024;
36149f464c52Smaya
36159f464c52Smaya    if (!is_multiline)
36169f464c52Smaya    {
36179f464c52Smaya        RenderNavHighlight(frame_bb, id);
36189f464c52Smaya        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
36199f464c52Smaya    }
36209f464c52Smaya
36219f464c52Smaya    const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
36229f464c52Smaya    ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
36239f464c52Smaya    ImVec2 text_size(0.f, 0.f);
36249f464c52Smaya    const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
36259f464c52Smaya    if (g.ActiveId == id || is_currently_scrolling)
36269f464c52Smaya    {
36279f464c52Smaya        edit_state.CursorAnim += io.DeltaTime;
36289f464c52Smaya
36299f464c52Smaya        // This is going to be messy. We need to:
36309f464c52Smaya        // - Display the text (this alone can be more easily clipped)
36319f464c52Smaya        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
36329f464c52Smaya        // - Measure text height (for scrollbar)
36339f464c52Smaya        // 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)
36349f464c52Smaya        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
36359f464c52Smaya        const ImWchar* text_begin = edit_state.TextW.Data;
36369f464c52Smaya        ImVec2 cursor_offset, select_start_offset;
36379f464c52Smaya
36389f464c52Smaya        {
36399f464c52Smaya            // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
36409f464c52Smaya            const ImWchar* searches_input_ptr[2];
36419f464c52Smaya            searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
36429f464c52Smaya            searches_input_ptr[1] = NULL;
36439f464c52Smaya            int searches_remaining = 1;
36449f464c52Smaya            int searches_result_line_number[2] = { -1, -999 };
36459f464c52Smaya            if (edit_state.StbState.select_start != edit_state.StbState.select_end)
36469f464c52Smaya            {
36479f464c52Smaya                searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
36489f464c52Smaya                searches_result_line_number[1] = -1;
36499f464c52Smaya                searches_remaining++;
36509f464c52Smaya            }
36519f464c52Smaya
36529f464c52Smaya            // Iterate all lines to find our line numbers
36539f464c52Smaya            // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
36549f464c52Smaya            searches_remaining += is_multiline ? 1 : 0;
36559f464c52Smaya            int line_count = 0;
36569f464c52Smaya            //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-bits
36579f464c52Smaya            for (const ImWchar* s = text_begin; *s != 0; s++)
36589f464c52Smaya                if (*s == '\n')
36599f464c52Smaya                {
36609f464c52Smaya                    line_count++;
36619f464c52Smaya                    if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
36629f464c52Smaya                    if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
36639f464c52Smaya                }
36649f464c52Smaya            line_count++;
36659f464c52Smaya            if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
36669f464c52Smaya            if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
36679f464c52Smaya
36689f464c52Smaya            // Calculate 2d position by finding the beginning of the line and measuring distance
36699f464c52Smaya            cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
36709f464c52Smaya            cursor_offset.y = searches_result_line_number[0] * g.FontSize;
36719f464c52Smaya            if (searches_result_line_number[1] >= 0)
36729f464c52Smaya            {
36739f464c52Smaya                select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
36749f464c52Smaya                select_start_offset.y = searches_result_line_number[1] * g.FontSize;
36759f464c52Smaya            }
36769f464c52Smaya
36779f464c52Smaya            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
36789f464c52Smaya            if (is_multiline)
36799f464c52Smaya                text_size = ImVec2(size.x, line_count * g.FontSize);
36809f464c52Smaya        }
36819f464c52Smaya
36829f464c52Smaya        // Scroll
36839f464c52Smaya        if (edit_state.CursorFollow)
36849f464c52Smaya        {
36859f464c52Smaya            // Horizontal scroll in chunks of quarter width
36869f464c52Smaya            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
36879f464c52Smaya            {
36889f464c52Smaya                const float scroll_increment_x = size.x * 0.25f;
36899f464c52Smaya                if (cursor_offset.x < edit_state.ScrollX)
36909f464c52Smaya                    edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
36919f464c52Smaya                else if (cursor_offset.x - size.x >= edit_state.ScrollX)
36929f464c52Smaya                    edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
36939f464c52Smaya            }
36949f464c52Smaya            else
36959f464c52Smaya            {
36969f464c52Smaya                edit_state.ScrollX = 0.0f;
36979f464c52Smaya            }
36989f464c52Smaya
36999f464c52Smaya            // Vertical scroll
37009f464c52Smaya            if (is_multiline)
37019f464c52Smaya            {
37029f464c52Smaya                float scroll_y = draw_window->Scroll.y;
37039f464c52Smaya                if (cursor_offset.y - g.FontSize < scroll_y)
37049f464c52Smaya                    scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
37059f464c52Smaya                else if (cursor_offset.y - size.y >= scroll_y)
37069f464c52Smaya                    scroll_y = cursor_offset.y - size.y;
37079f464c52Smaya                draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y);   // To avoid a frame of lag
37089f464c52Smaya                draw_window->Scroll.y = scroll_y;
37099f464c52Smaya                render_pos.y = draw_window->DC.CursorPos.y;
37109f464c52Smaya            }
37119f464c52Smaya        }
37129f464c52Smaya        edit_state.CursorFollow = false;
37139f464c52Smaya        const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
37149f464c52Smaya
37159f464c52Smaya        // Draw selection
37169f464c52Smaya        if (edit_state.StbState.select_start != edit_state.StbState.select_end)
37179f464c52Smaya        {
37189f464c52Smaya            const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
37199f464c52Smaya            const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
37209f464c52Smaya
37219f464c52Smaya            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.
37229f464c52Smaya            float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
37239f464c52Smaya            ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
37249f464c52Smaya            ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
37259f464c52Smaya            for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
37269f464c52Smaya            {
37279f464c52Smaya                if (rect_pos.y > clip_rect.w + g.FontSize)
37289f464c52Smaya                    break;
37299f464c52Smaya                if (rect_pos.y < clip_rect.y)
37309f464c52Smaya                {
37319f464c52Smaya                    //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p);  // FIXME-OPT: Could use this when wchar_t are 16-bits
37329f464c52Smaya                    //p = p ? p + 1 : text_selected_end;
37339f464c52Smaya                    while (p < text_selected_end)
37349f464c52Smaya                        if (*p++ == '\n')
37359f464c52Smaya                            break;
37369f464c52Smaya                }
37379f464c52Smaya                else
37389f464c52Smaya                {
37399f464c52Smaya                    ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
37409f464c52Smaya                    if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
37419f464c52Smaya                    ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
37429f464c52Smaya                    rect.ClipWith(clip_rect);
37439f464c52Smaya                    if (rect.Overlaps(clip_rect))
37449f464c52Smaya                        draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
37459f464c52Smaya                }
37469f464c52Smaya                rect_pos.x = render_pos.x - render_scroll.x;
37479f464c52Smaya                rect_pos.y += g.FontSize;
37489f464c52Smaya            }
37499f464c52Smaya        }
37509f464c52Smaya
37519f464c52Smaya        const int buf_display_len = edit_state.CurLenA;
37529f464c52Smaya        if (is_multiline || buf_display_len < buf_display_max_length)
37539f464c52Smaya            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
37549f464c52Smaya
37559f464c52Smaya        // Draw blinking cursor
37569f464c52Smaya        bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
37579f464c52Smaya        ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
37589f464c52Smaya        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);
37599f464c52Smaya        if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
37609f464c52Smaya            draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
37619f464c52Smaya
37629f464c52Smaya        // 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.)
37639f464c52Smaya        if (is_editable)
37649f464c52Smaya            g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
37659f464c52Smaya    }
37669f464c52Smaya    else
37679f464c52Smaya    {
37689f464c52Smaya        // Render text only
37699f464c52Smaya        const char* buf_end = NULL;
37709f464c52Smaya        if (is_multiline)
37719f464c52Smaya            text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
37729f464c52Smaya        else
37739f464c52Smaya            buf_end = buf_display + strlen(buf_display);
37749f464c52Smaya        if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
37759f464c52Smaya            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
37769f464c52Smaya    }
37779f464c52Smaya
37789f464c52Smaya    if (is_multiline)
37799f464c52Smaya    {
37809f464c52Smaya        Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
37819f464c52Smaya        EndChildFrame();
37829f464c52Smaya        EndGroup();
37839f464c52Smaya    }
37849f464c52Smaya
37859f464c52Smaya    if (is_password)
37869f464c52Smaya        PopFont();
37879f464c52Smaya
37889f464c52Smaya    // Log as text
37899f464c52Smaya    if (g.LogEnabled && !is_password)
37909f464c52Smaya        LogRenderedText(&render_pos, buf_display, NULL);
37919f464c52Smaya
37929f464c52Smaya    if (label_size.x > 0)
37939f464c52Smaya        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
37949f464c52Smaya
37959f464c52Smaya    if (value_changed)
37969f464c52Smaya        MarkItemEdited(id);
37979f464c52Smaya
37989f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
37999f464c52Smaya    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
38009f464c52Smaya        return enter_pressed;
38019f464c52Smaya    else
38029f464c52Smaya        return value_changed;
38039f464c52Smaya}
38049f464c52Smaya
38059f464c52Smaya//-------------------------------------------------------------------------
38069f464c52Smaya// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
38079f464c52Smaya//-------------------------------------------------------------------------
38089f464c52Smaya// - ColorEdit3()
38099f464c52Smaya// - ColorEdit4()
38109f464c52Smaya// - ColorPicker3()
38119f464c52Smaya// - RenderColorRectWithAlphaCheckerboard() [Internal]
38129f464c52Smaya// - ColorPicker4()
38139f464c52Smaya// - ColorButton()
38149f464c52Smaya// - SetColorEditOptions()
38159f464c52Smaya// - ColorTooltip() [Internal]
38169f464c52Smaya// - ColorEditOptionsPopup() [Internal]
38179f464c52Smaya// - ColorPickerOptionsPopup() [Internal]
38189f464c52Smaya//-------------------------------------------------------------------------
38199f464c52Smaya
38209f464c52Smayabool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
38219f464c52Smaya{
38229f464c52Smaya    return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
38239f464c52Smaya}
38249f464c52Smaya
38259f464c52Smaya// Edit colors components (each component in 0.0f..1.0f range).
38269f464c52Smaya// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
38279f464c52Smaya// 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.
38289f464c52Smayabool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
38299f464c52Smaya{
38309f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
38319f464c52Smaya    if (window->SkipItems)
38329f464c52Smaya        return false;
38339f464c52Smaya
38349f464c52Smaya    ImGuiContext& g = *GImGui;
38359f464c52Smaya    const ImGuiStyle& style = g.Style;
38369f464c52Smaya    const float square_sz = GetFrameHeight();
38379f464c52Smaya    const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
38389f464c52Smaya    const float w_items_all = CalcItemWidth() - w_extra;
38399f464c52Smaya    const char* label_display_end = FindRenderedTextEnd(label);
38409f464c52Smaya
38419f464c52Smaya    BeginGroup();
38429f464c52Smaya    PushID(label);
38439f464c52Smaya
38449f464c52Smaya    // If we're not showing any slider there's no point in doing any HSV conversions
38459f464c52Smaya    const ImGuiColorEditFlags flags_untouched = flags;
38469f464c52Smaya    if (flags & ImGuiColorEditFlags_NoInputs)
38479f464c52Smaya        flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
38489f464c52Smaya
38499f464c52Smaya    // Context menu: display and modify options (before defaults are applied)
38509f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoOptions))
38519f464c52Smaya        ColorEditOptionsPopup(col, flags);
38529f464c52Smaya
38539f464c52Smaya    // Read stored options
38549f464c52Smaya    if (!(flags & ImGuiColorEditFlags__InputsMask))
38559f464c52Smaya        flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
38569f464c52Smaya    if (!(flags & ImGuiColorEditFlags__DataTypeMask))
38579f464c52Smaya        flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
38589f464c52Smaya    if (!(flags & ImGuiColorEditFlags__PickerMask))
38599f464c52Smaya        flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
38609f464c52Smaya    flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
38619f464c52Smaya
38629f464c52Smaya    const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
38639f464c52Smaya    const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
38649f464c52Smaya    const int components = alpha ? 4 : 3;
38659f464c52Smaya
38669f464c52Smaya    // Convert to the formats we need
38679f464c52Smaya    float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
38689f464c52Smaya    if (flags & ImGuiColorEditFlags_HSV)
38699f464c52Smaya        ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
38709f464c52Smaya    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]) };
38719f464c52Smaya
38729f464c52Smaya    bool value_changed = false;
38739f464c52Smaya    bool value_changed_as_float = false;
38749f464c52Smaya
38759f464c52Smaya    if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
38769f464c52Smaya    {
38779f464c52Smaya        // RGB/HSV 0..255 Sliders
38789f464c52Smaya        const float w_item_one  = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
38799f464c52Smaya        const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
38809f464c52Smaya
38819f464c52Smaya        const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
38829f464c52Smaya        const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
38839f464c52Smaya        const char* fmt_table_int[3][4] =
38849f464c52Smaya        {
38859f464c52Smaya            {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
38869f464c52Smaya            { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
38879f464c52Smaya            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
38889f464c52Smaya        };
38899f464c52Smaya        const char* fmt_table_float[3][4] =
38909f464c52Smaya        {
38919f464c52Smaya            {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
38929f464c52Smaya            { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
38939f464c52Smaya            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
38949f464c52Smaya        };
38959f464c52Smaya        const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
38969f464c52Smaya
38979f464c52Smaya        PushItemWidth(w_item_one);
38989f464c52Smaya        for (int n = 0; n < components; n++)
38999f464c52Smaya        {
39009f464c52Smaya            if (n > 0)
39019f464c52Smaya                SameLine(0, style.ItemInnerSpacing.x);
39029f464c52Smaya            if (n + 1 == components)
39039f464c52Smaya                PushItemWidth(w_item_last);
39049f464c52Smaya            if (flags & ImGuiColorEditFlags_Float)
39059f464c52Smaya            {
39069f464c52Smaya                value_changed |= DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
39079f464c52Smaya                value_changed_as_float |= value_changed;
39089f464c52Smaya            }
39099f464c52Smaya            else
39109f464c52Smaya            {
39119f464c52Smaya                value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
39129f464c52Smaya            }
39139f464c52Smaya            if (!(flags & ImGuiColorEditFlags_NoOptions))
39149f464c52Smaya                OpenPopupOnItemClick("context");
39159f464c52Smaya        }
39169f464c52Smaya        PopItemWidth();
39179f464c52Smaya        PopItemWidth();
39189f464c52Smaya    }
39199f464c52Smaya    else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
39209f464c52Smaya    {
39219f464c52Smaya        // RGB Hexadecimal Input
39229f464c52Smaya        char buf[64];
39239f464c52Smaya        if (alpha)
39249f464c52Smaya            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));
39259f464c52Smaya        else
39269f464c52Smaya            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
39279f464c52Smaya        PushItemWidth(w_items_all);
39289f464c52Smaya        if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
39299f464c52Smaya        {
39309f464c52Smaya            value_changed = true;
39319f464c52Smaya            char* p = buf;
39329f464c52Smaya            while (*p == '#' || ImCharIsBlankA(*p))
39339f464c52Smaya                p++;
39349f464c52Smaya            i[0] = i[1] = i[2] = i[3] = 0;
39359f464c52Smaya            if (alpha)
39369f464c52Smaya                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)
39379f464c52Smaya            else
39389f464c52Smaya                sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
39399f464c52Smaya        }
39409f464c52Smaya        if (!(flags & ImGuiColorEditFlags_NoOptions))
39419f464c52Smaya            OpenPopupOnItemClick("context");
39429f464c52Smaya        PopItemWidth();
39439f464c52Smaya    }
39449f464c52Smaya
39459f464c52Smaya    ImGuiWindow* picker_active_window = NULL;
39469f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
39479f464c52Smaya    {
39489f464c52Smaya        if (!(flags & ImGuiColorEditFlags_NoInputs))
39499f464c52Smaya            SameLine(0, style.ItemInnerSpacing.x);
39509f464c52Smaya
39519f464c52Smaya        const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
39529f464c52Smaya        if (ColorButton("##ColorButton", col_v4, flags))
39539f464c52Smaya        {
39549f464c52Smaya            if (!(flags & ImGuiColorEditFlags_NoPicker))
39559f464c52Smaya            {
39569f464c52Smaya                // Store current color and open a picker
39579f464c52Smaya                g.ColorPickerRef = col_v4;
39589f464c52Smaya                OpenPopup("picker");
39599f464c52Smaya                SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
39609f464c52Smaya            }
39619f464c52Smaya        }
39629f464c52Smaya        if (!(flags & ImGuiColorEditFlags_NoOptions))
39639f464c52Smaya            OpenPopupOnItemClick("context");
39649f464c52Smaya
39659f464c52Smaya        if (BeginPopup("picker"))
39669f464c52Smaya        {
39679f464c52Smaya            picker_active_window = g.CurrentWindow;
39689f464c52Smaya            if (label != label_display_end)
39699f464c52Smaya            {
39709f464c52Smaya                TextUnformatted(label, label_display_end);
39719f464c52Smaya                Spacing();
39729f464c52Smaya            }
39739f464c52Smaya            ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
39749f464c52Smaya            ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
39759f464c52Smaya            PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
39769f464c52Smaya            value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
39779f464c52Smaya            PopItemWidth();
39789f464c52Smaya            EndPopup();
39799f464c52Smaya        }
39809f464c52Smaya    }
39819f464c52Smaya
39829f464c52Smaya    if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
39839f464c52Smaya    {
39849f464c52Smaya        SameLine(0, style.ItemInnerSpacing.x);
39859f464c52Smaya        TextUnformatted(label, label_display_end);
39869f464c52Smaya    }
39879f464c52Smaya
39889f464c52Smaya    // Convert back
39899f464c52Smaya    if (picker_active_window == NULL)
39909f464c52Smaya    {
39919f464c52Smaya        if (!value_changed_as_float)
39929f464c52Smaya            for (int n = 0; n < 4; n++)
39939f464c52Smaya                f[n] = i[n] / 255.0f;
39949f464c52Smaya        if (flags & ImGuiColorEditFlags_HSV)
39959f464c52Smaya            ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
39969f464c52Smaya        if (value_changed)
39979f464c52Smaya        {
39989f464c52Smaya            col[0] = f[0];
39999f464c52Smaya            col[1] = f[1];
40009f464c52Smaya            col[2] = f[2];
40019f464c52Smaya            if (alpha)
40029f464c52Smaya                col[3] = f[3];
40039f464c52Smaya        }
40049f464c52Smaya    }
40059f464c52Smaya
40069f464c52Smaya    PopID();
40079f464c52Smaya    EndGroup();
40089f464c52Smaya
40099f464c52Smaya    // Drag and Drop Target
40109f464c52Smaya    // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
40119f464c52Smaya    if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
40129f464c52Smaya    {
40139f464c52Smaya        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
40149f464c52Smaya        {
40159f464c52Smaya            memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
40169f464c52Smaya            value_changed = true;
40179f464c52Smaya        }
40189f464c52Smaya        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
40199f464c52Smaya        {
40209f464c52Smaya            memcpy((float*)col, payload->Data, sizeof(float) * components);
40219f464c52Smaya            value_changed = true;
40229f464c52Smaya        }
40239f464c52Smaya        EndDragDropTarget();
40249f464c52Smaya    }
40259f464c52Smaya
40269f464c52Smaya    // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
40279f464c52Smaya    if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
40289f464c52Smaya        window->DC.LastItemId = g.ActiveId;
40299f464c52Smaya
40309f464c52Smaya    if (value_changed)
40319f464c52Smaya        MarkItemEdited(window->DC.LastItemId);
40329f464c52Smaya
40339f464c52Smaya    return value_changed;
40349f464c52Smaya}
40359f464c52Smaya
40369f464c52Smayabool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
40379f464c52Smaya{
40389f464c52Smaya    float col4[4] = { col[0], col[1], col[2], 1.0f };
40399f464c52Smaya    if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
40409f464c52Smaya        return false;
40419f464c52Smaya    col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
40429f464c52Smaya    return true;
40439f464c52Smaya}
40449f464c52Smaya
40459f464c52Smayastatic inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
40469f464c52Smaya{
40479f464c52Smaya    float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
40489f464c52Smaya    int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
40499f464c52Smaya    int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
40509f464c52Smaya    int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
40519f464c52Smaya    return IM_COL32(r, g, b, 0xFF);
40529f464c52Smaya}
40539f464c52Smaya
40549f464c52Smaya// Helper for ColorPicker4()
40559f464c52Smaya// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
40569f464c52Smaya// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
40579f464c52Smayavoid ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
40589f464c52Smaya{
40599f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
40609f464c52Smaya    if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
40619f464c52Smaya    {
40629f464c52Smaya        ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
40639f464c52Smaya        ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
40649f464c52Smaya        window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
40659f464c52Smaya
40669f464c52Smaya        int yi = 0;
40679f464c52Smaya        for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
40689f464c52Smaya        {
40699f464c52Smaya            float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
40709f464c52Smaya            if (y2 <= y1)
40719f464c52Smaya                continue;
40729f464c52Smaya            for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
40739f464c52Smaya            {
40749f464c52Smaya                float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
40759f464c52Smaya                if (x2 <= x1)
40769f464c52Smaya                    continue;
40779f464c52Smaya                int rounding_corners_flags_cell = 0;
40789f464c52Smaya                if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
40799f464c52Smaya                if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
40809f464c52Smaya                rounding_corners_flags_cell &= rounding_corners_flags;
40819f464c52Smaya                window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
40829f464c52Smaya            }
40839f464c52Smaya        }
40849f464c52Smaya    }
40859f464c52Smaya    else
40869f464c52Smaya    {
40879f464c52Smaya        window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
40889f464c52Smaya    }
40899f464c52Smaya}
40909f464c52Smaya
40919f464c52Smaya// Helper for ColorPicker4()
40929f464c52Smayastatic void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
40939f464c52Smaya{
40949f464c52Smaya    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_BLACK);
40959f464c52Smaya    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32_WHITE);
40969f464c52Smaya    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_BLACK);
40979f464c52Smaya    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32_WHITE);
40989f464c52Smaya}
40999f464c52Smaya
41009f464c52Smaya// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
41019f464c52Smaya// 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..)
41029f464c52Smayabool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
41039f464c52Smaya{
41049f464c52Smaya    ImGuiContext& g = *GImGui;
41059f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
41069f464c52Smaya    ImDrawList* draw_list = window->DrawList;
41079f464c52Smaya
41089f464c52Smaya    ImGuiStyle& style = g.Style;
41099f464c52Smaya    ImGuiIO& io = g.IO;
41109f464c52Smaya
41119f464c52Smaya    PushID(label);
41129f464c52Smaya    BeginGroup();
41139f464c52Smaya
41149f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
41159f464c52Smaya        flags |= ImGuiColorEditFlags_NoSmallPreview;
41169f464c52Smaya
41179f464c52Smaya    // Context menu: display and store options.
41189f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoOptions))
41199f464c52Smaya        ColorPickerOptionsPopup(col, flags);
41209f464c52Smaya
41219f464c52Smaya    // Read stored options
41229f464c52Smaya    if (!(flags & ImGuiColorEditFlags__PickerMask))
41239f464c52Smaya        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
41249f464c52Smaya    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
41259f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoOptions))
41269f464c52Smaya        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
41279f464c52Smaya
41289f464c52Smaya    // Setup
41299f464c52Smaya    int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
41309f464c52Smaya    bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
41319f464c52Smaya    ImVec2 picker_pos = window->DC.CursorPos;
41329f464c52Smaya    float square_sz = GetFrameHeight();
41339f464c52Smaya    float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
41349f464c52Smaya    float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
41359f464c52Smaya    float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
41369f464c52Smaya    float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
41379f464c52Smaya    float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
41389f464c52Smaya
41399f464c52Smaya    float backup_initial_col[4];
41409f464c52Smaya    memcpy(backup_initial_col, col, components * sizeof(float));
41419f464c52Smaya
41429f464c52Smaya    float wheel_thickness = sv_picker_size * 0.08f;
41439f464c52Smaya    float wheel_r_outer = sv_picker_size * 0.50f;
41449f464c52Smaya    float wheel_r_inner = wheel_r_outer - wheel_thickness;
41459f464c52Smaya    ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
41469f464c52Smaya
41479f464c52Smaya    // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
41489f464c52Smaya    float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
41499f464c52Smaya    ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
41509f464c52Smaya    ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
41519f464c52Smaya    ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
41529f464c52Smaya
41539f464c52Smaya    float H,S,V;
41549f464c52Smaya    ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
41559f464c52Smaya
41569f464c52Smaya    bool value_changed = false, value_changed_h = false, value_changed_sv = false;
41579f464c52Smaya
41589f464c52Smaya    PushItemFlag(ImGuiItemFlags_NoNav, true);
41599f464c52Smaya    if (flags & ImGuiColorEditFlags_PickerHueWheel)
41609f464c52Smaya    {
41619f464c52Smaya        // Hue wheel + SV triangle logic
41629f464c52Smaya        InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
41639f464c52Smaya        if (IsItemActive())
41649f464c52Smaya        {
41659f464c52Smaya            ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
41669f464c52Smaya            ImVec2 current_off = g.IO.MousePos - wheel_center;
41679f464c52Smaya            float initial_dist2 = ImLengthSqr(initial_off);
41689f464c52Smaya            if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
41699f464c52Smaya            {
41709f464c52Smaya                // Interactive with Hue wheel
41719f464c52Smaya                H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
41729f464c52Smaya                if (H < 0.0f)
41739f464c52Smaya                    H += 1.0f;
41749f464c52Smaya                value_changed = value_changed_h = true;
41759f464c52Smaya            }
41769f464c52Smaya            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
41779f464c52Smaya            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
41789f464c52Smaya            if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
41799f464c52Smaya            {
41809f464c52Smaya                // Interacting with SV triangle
41819f464c52Smaya                ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
41829f464c52Smaya                if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
41839f464c52Smaya                    current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
41849f464c52Smaya                float uu, vv, ww;
41859f464c52Smaya                ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
41869f464c52Smaya                V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
41879f464c52Smaya                S = ImClamp(uu / V, 0.0001f, 1.0f);
41889f464c52Smaya                value_changed = value_changed_sv = true;
41899f464c52Smaya            }
41909f464c52Smaya        }
41919f464c52Smaya        if (!(flags & ImGuiColorEditFlags_NoOptions))
41929f464c52Smaya            OpenPopupOnItemClick("context");
41939f464c52Smaya    }
41949f464c52Smaya    else if (flags & ImGuiColorEditFlags_PickerHueBar)
41959f464c52Smaya    {
41969f464c52Smaya        // SV rectangle logic
41979f464c52Smaya        InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
41989f464c52Smaya        if (IsItemActive())
41999f464c52Smaya        {
42009f464c52Smaya            S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
42019f464c52Smaya            V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
42029f464c52Smaya            value_changed = value_changed_sv = true;
42039f464c52Smaya        }
42049f464c52Smaya        if (!(flags & ImGuiColorEditFlags_NoOptions))
42059f464c52Smaya            OpenPopupOnItemClick("context");
42069f464c52Smaya
42079f464c52Smaya        // Hue bar logic
42089f464c52Smaya        SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
42099f464c52Smaya        InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
42109f464c52Smaya        if (IsItemActive())
42119f464c52Smaya        {
42129f464c52Smaya            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
42139f464c52Smaya            value_changed = value_changed_h = true;
42149f464c52Smaya        }
42159f464c52Smaya    }
42169f464c52Smaya
42179f464c52Smaya    // Alpha bar logic
42189f464c52Smaya    if (alpha_bar)
42199f464c52Smaya    {
42209f464c52Smaya        SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
42219f464c52Smaya        InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
42229f464c52Smaya        if (IsItemActive())
42239f464c52Smaya        {
42249f464c52Smaya            col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
42259f464c52Smaya            value_changed = true;
42269f464c52Smaya        }
42279f464c52Smaya    }
42289f464c52Smaya    PopItemFlag(); // ImGuiItemFlags_NoNav
42299f464c52Smaya
42309f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
42319f464c52Smaya    {
42329f464c52Smaya        SameLine(0, style.ItemInnerSpacing.x);
42339f464c52Smaya        BeginGroup();
42349f464c52Smaya    }
42359f464c52Smaya
42369f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoLabel))
42379f464c52Smaya    {
42389f464c52Smaya        const char* label_display_end = FindRenderedTextEnd(label);
42399f464c52Smaya        if (label != label_display_end)
42409f464c52Smaya        {
42419f464c52Smaya            if ((flags & ImGuiColorEditFlags_NoSidePreview))
42429f464c52Smaya                SameLine(0, style.ItemInnerSpacing.x);
42439f464c52Smaya            TextUnformatted(label, label_display_end);
42449f464c52Smaya        }
42459f464c52Smaya    }
42469f464c52Smaya
42479f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
42489f464c52Smaya    {
42499f464c52Smaya        PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
42509f464c52Smaya        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
42519f464c52Smaya        if ((flags & ImGuiColorEditFlags_NoLabel))
42529f464c52Smaya            Text("Current");
42539f464c52Smaya        ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
42549f464c52Smaya        if (ref_col != NULL)
42559f464c52Smaya        {
42569f464c52Smaya            Text("Original");
42579f464c52Smaya            ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
42589f464c52Smaya            if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
42599f464c52Smaya            {
42609f464c52Smaya                memcpy(col, ref_col, components * sizeof(float));
42619f464c52Smaya                value_changed = true;
42629f464c52Smaya            }
42639f464c52Smaya        }
42649f464c52Smaya        PopItemFlag();
42659f464c52Smaya        EndGroup();
42669f464c52Smaya    }
42679f464c52Smaya
42689f464c52Smaya    // Convert back color to RGB
42699f464c52Smaya    if (value_changed_h || value_changed_sv)
42709f464c52Smaya        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]);
42719f464c52Smaya
42729f464c52Smaya    // R,G,B and H,S,V slider color editor
42739f464c52Smaya    bool value_changed_fix_hue_wrap = false;
42749f464c52Smaya    if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
42759f464c52Smaya    {
42769f464c52Smaya        PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
42779f464c52Smaya        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
42789f464c52Smaya        ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
42799f464c52Smaya        if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
42809f464c52Smaya            if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB))
42819f464c52Smaya            {
42829f464c52Smaya                // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
42839f464c52Smaya                // 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)
42849f464c52Smaya                value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
42859f464c52Smaya                value_changed = true;
42869f464c52Smaya            }
42879f464c52Smaya        if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
42889f464c52Smaya            value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
42899f464c52Smaya        if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
42909f464c52Smaya            value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
42919f464c52Smaya        PopItemWidth();
42929f464c52Smaya    }
42939f464c52Smaya
42949f464c52Smaya    // Try to cancel hue wrap (after ColorEdit4 call), if any
42959f464c52Smaya    if (value_changed_fix_hue_wrap)
42969f464c52Smaya    {
42979f464c52Smaya        float new_H, new_S, new_V;
42989f464c52Smaya        ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
42999f464c52Smaya        if (new_H <= 0 && H > 0)
43009f464c52Smaya        {
43019f464c52Smaya            if (new_V <= 0 && V != new_V)
43029f464c52Smaya                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
43039f464c52Smaya            else if (new_S <= 0)
43049f464c52Smaya                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
43059f464c52Smaya        }
43069f464c52Smaya    }
43079f464c52Smaya
43089f464c52Smaya    ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
43099f464c52Smaya    ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
43109f464c52Smaya    ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
43119f464c52Smaya
43129f464c52Smaya    const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
43139f464c52Smaya    ImVec2 sv_cursor_pos;
43149f464c52Smaya
43159f464c52Smaya    if (flags & ImGuiColorEditFlags_PickerHueWheel)
43169f464c52Smaya    {
43179f464c52Smaya        // Render Hue Wheel
43189f464c52Smaya        const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
43199f464c52Smaya        const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
43209f464c52Smaya        for (int n = 0; n < 6; n++)
43219f464c52Smaya        {
43229f464c52Smaya            const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
43239f464c52Smaya            const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
43249f464c52Smaya            const int vert_start_idx = draw_list->VtxBuffer.Size;
43259f464c52Smaya            draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
43269f464c52Smaya            draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
43279f464c52Smaya            const int vert_end_idx = draw_list->VtxBuffer.Size;
43289f464c52Smaya
43299f464c52Smaya            // Paint colors over existing vertices
43309f464c52Smaya            ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
43319f464c52Smaya            ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
43329f464c52Smaya            ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);
43339f464c52Smaya        }
43349f464c52Smaya
43359f464c52Smaya        // Render Cursor + preview on Hue Wheel
43369f464c52Smaya        float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
43379f464c52Smaya        float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
43389f464c52Smaya        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);
43399f464c52Smaya        float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
43409f464c52Smaya        int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
43419f464c52Smaya        draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
43429f464c52Smaya        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
43439f464c52Smaya        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
43449f464c52Smaya
43459f464c52Smaya        // Render SV triangle (rotated according to hue)
43469f464c52Smaya        ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
43479f464c52Smaya        ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
43489f464c52Smaya        ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
43499f464c52Smaya        ImVec2 uv_white = GetFontTexUvWhitePixel();
43509f464c52Smaya        draw_list->PrimReserve(6, 6);
43519f464c52Smaya        draw_list->PrimVtx(tra, uv_white, hue_color32);
43529f464c52Smaya        draw_list->PrimVtx(trb, uv_white, hue_color32);
43539f464c52Smaya        draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
43549f464c52Smaya        draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
43559f464c52Smaya        draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
43569f464c52Smaya        draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
43579f464c52Smaya        draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
43589f464c52Smaya        sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
43599f464c52Smaya    }
43609f464c52Smaya    else if (flags & ImGuiColorEditFlags_PickerHueBar)
43619f464c52Smaya    {
43629f464c52Smaya        // Render SV Square
43639f464c52Smaya        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
43649f464c52Smaya        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
43659f464c52Smaya        RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
43669f464c52Smaya        sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S)     * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
43679f464c52Smaya        sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
43689f464c52Smaya
43699f464c52Smaya        // Render Hue Bar
43709f464c52Smaya        for (int i = 0; i < 6; ++i)
43719f464c52Smaya            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)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]);
43729f464c52Smaya        float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
43739f464c52Smaya        RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
43749f464c52Smaya        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);
43759f464c52Smaya    }
43769f464c52Smaya
43779f464c52Smaya    // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
43789f464c52Smaya    float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
43799f464c52Smaya    draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
43809f464c52Smaya    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
43819f464c52Smaya    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
43829f464c52Smaya
43839f464c52Smaya    // Render alpha bar
43849f464c52Smaya    if (alpha_bar)
43859f464c52Smaya    {
43869f464c52Smaya        float alpha = ImSaturate(col[3]);
43879f464c52Smaya        ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
43889f464c52Smaya        RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
43899f464c52Smaya        draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK);
43909f464c52Smaya        float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
43919f464c52Smaya        RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
43929f464c52Smaya        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);
43939f464c52Smaya    }
43949f464c52Smaya
43959f464c52Smaya    EndGroup();
43969f464c52Smaya
43979f464c52Smaya    if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
43989f464c52Smaya        value_changed = false;
43999f464c52Smaya    if (value_changed)
44009f464c52Smaya        MarkItemEdited(window->DC.LastItemId);
44019f464c52Smaya
44029f464c52Smaya    PopID();
44039f464c52Smaya
44049f464c52Smaya    return value_changed;
44059f464c52Smaya}
44069f464c52Smaya
44079f464c52Smaya// A little colored square. Return true when clicked.
44089f464c52Smaya// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
44099f464c52Smaya// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
44109f464c52Smayabool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
44119f464c52Smaya{
44129f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
44139f464c52Smaya    if (window->SkipItems)
44149f464c52Smaya        return false;
44159f464c52Smaya
44169f464c52Smaya    ImGuiContext& g = *GImGui;
44179f464c52Smaya    const ImGuiID id = window->GetID(desc_id);
44189f464c52Smaya    float default_size = GetFrameHeight();
44199f464c52Smaya    if (size.x == 0.0f)
44209f464c52Smaya        size.x = default_size;
44219f464c52Smaya    if (size.y == 0.0f)
44229f464c52Smaya        size.y = default_size;
44239f464c52Smaya    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
44249f464c52Smaya    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
44259f464c52Smaya    if (!ItemAdd(bb, id))
44269f464c52Smaya        return false;
44279f464c52Smaya
44289f464c52Smaya    bool hovered, held;
44299f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
44309f464c52Smaya
44319f464c52Smaya    if (flags & ImGuiColorEditFlags_NoAlpha)
44329f464c52Smaya        flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
44339f464c52Smaya
44349f464c52Smaya    ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
44359f464c52Smaya    float grid_step = ImMin(size.x, size.y) / 2.99f;
44369f464c52Smaya    float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
44379f464c52Smaya    ImRect bb_inner = bb;
44389f464c52Smaya    float 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.
44399f464c52Smaya    bb_inner.Expand(off);
44409f464c52Smaya    if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
44419f464c52Smaya    {
44429f464c52Smaya        float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
44439f464c52Smaya        RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
44449f464c52Smaya        window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
44459f464c52Smaya    }
44469f464c52Smaya    else
44479f464c52Smaya    {
44489f464c52Smaya        // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
44499f464c52Smaya        ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
44509f464c52Smaya        if (col_source.w < 1.0f)
44519f464c52Smaya            RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
44529f464c52Smaya        else
44539f464c52Smaya            window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
44549f464c52Smaya    }
44559f464c52Smaya    RenderNavHighlight(bb, id);
44569f464c52Smaya    if (g.Style.FrameBorderSize > 0.0f)
44579f464c52Smaya        RenderFrameBorder(bb.Min, bb.Max, rounding);
44589f464c52Smaya    else
44599f464c52Smaya        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
44609f464c52Smaya
44619f464c52Smaya    // Drag and Drop Source
44629f464c52Smaya    // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
44639f464c52Smaya    if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
44649f464c52Smaya    {
44659f464c52Smaya        if (flags & ImGuiColorEditFlags_NoAlpha)
44669f464c52Smaya            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
44679f464c52Smaya        else
44689f464c52Smaya            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
44699f464c52Smaya        ColorButton(desc_id, col, flags);
44709f464c52Smaya        SameLine();
44719f464c52Smaya        TextUnformatted("Color");
44729f464c52Smaya        EndDragDropSource();
44739f464c52Smaya    }
44749f464c52Smaya
44759f464c52Smaya    // Tooltip
44769f464c52Smaya    if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
44779f464c52Smaya        ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
44789f464c52Smaya
44799f464c52Smaya    if (pressed)
44809f464c52Smaya        MarkItemEdited(id);
44819f464c52Smaya
44829f464c52Smaya    return pressed;
44839f464c52Smaya}
44849f464c52Smaya
44859f464c52Smayavoid ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
44869f464c52Smaya{
44879f464c52Smaya    ImGuiContext& g = *GImGui;
44889f464c52Smaya    if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
44899f464c52Smaya        flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
44909f464c52Smaya    if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
44919f464c52Smaya        flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
44929f464c52Smaya    if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
44939f464c52Smaya        flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
44949f464c52Smaya    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask)));   // Check only 1 option is selected
44959f464c52Smaya    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
44969f464c52Smaya    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask)));   // Check only 1 option is selected
44979f464c52Smaya    g.ColorEditOptions = flags;
44989f464c52Smaya}
44999f464c52Smaya
45009f464c52Smaya// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
45019f464c52Smayavoid ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
45029f464c52Smaya{
45039f464c52Smaya    ImGuiContext& g = *GImGui;
45049f464c52Smaya
45059f464c52Smaya    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]);
45069f464c52Smaya    BeginTooltipEx(0, true);
45079f464c52Smaya
45089f464c52Smaya    const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
45099f464c52Smaya    if (text_end > text)
45109f464c52Smaya    {
45119f464c52Smaya        TextUnformatted(text, text_end);
45129f464c52Smaya        Separator();
45139f464c52Smaya    }
45149f464c52Smaya
45159f464c52Smaya    ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
45169f464c52Smaya    ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
45179f464c52Smaya    SameLine();
45189f464c52Smaya    if (flags & ImGuiColorEditFlags_NoAlpha)
45199f464c52Smaya        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]);
45209f464c52Smaya    else
45219f464c52Smaya        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]);
45229f464c52Smaya    EndTooltip();
45239f464c52Smaya}
45249f464c52Smaya
45259f464c52Smayavoid ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
45269f464c52Smaya{
45279f464c52Smaya    bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
45289f464c52Smaya    bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
45299f464c52Smaya    if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
45309f464c52Smaya        return;
45319f464c52Smaya    ImGuiContext& g = *GImGui;
45329f464c52Smaya    ImGuiColorEditFlags opts = g.ColorEditOptions;
45339f464c52Smaya    if (allow_opt_inputs)
45349f464c52Smaya    {
45359f464c52Smaya        if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
45369f464c52Smaya        if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
45379f464c52Smaya        if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
45389f464c52Smaya    }
45399f464c52Smaya    if (allow_opt_datatype)
45409f464c52Smaya    {
45419f464c52Smaya        if (allow_opt_inputs) Separator();
45429f464c52Smaya        if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
45439f464c52Smaya        if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
45449f464c52Smaya    }
45459f464c52Smaya
45469f464c52Smaya    if (allow_opt_inputs || allow_opt_datatype)
45479f464c52Smaya        Separator();
45489f464c52Smaya    if (Button("Copy as..", ImVec2(-1,0)))
45499f464c52Smaya        OpenPopup("Copy");
45509f464c52Smaya    if (BeginPopup("Copy"))
45519f464c52Smaya    {
45529f464c52Smaya        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]);
45539f464c52Smaya        char buf[64];
45549f464c52Smaya        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
45559f464c52Smaya        if (Selectable(buf))
45569f464c52Smaya            SetClipboardText(buf);
45579f464c52Smaya        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
45589f464c52Smaya        if (Selectable(buf))
45599f464c52Smaya            SetClipboardText(buf);
45609f464c52Smaya        if (flags & ImGuiColorEditFlags_NoAlpha)
45619f464c52Smaya            ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
45629f464c52Smaya        else
45639f464c52Smaya            ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
45649f464c52Smaya        if (Selectable(buf))
45659f464c52Smaya            SetClipboardText(buf);
45669f464c52Smaya        EndPopup();
45679f464c52Smaya    }
45689f464c52Smaya
45699f464c52Smaya    g.ColorEditOptions = opts;
45709f464c52Smaya    EndPopup();
45719f464c52Smaya}
45729f464c52Smaya
45739f464c52Smayavoid ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
45749f464c52Smaya{
45759f464c52Smaya    bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
45769f464c52Smaya    bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
45779f464c52Smaya    if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
45789f464c52Smaya        return;
45799f464c52Smaya    ImGuiContext& g = *GImGui;
45809f464c52Smaya    if (allow_opt_picker)
45819f464c52Smaya    {
45829f464c52Smaya        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
45839f464c52Smaya        PushItemWidth(picker_size.x);
45849f464c52Smaya        for (int picker_type = 0; picker_type < 2; picker_type++)
45859f464c52Smaya        {
45869f464c52Smaya            // Draw small/thumbnail version of each picker type (over an invisible button for selection)
45879f464c52Smaya            if (picker_type > 0) Separator();
45889f464c52Smaya            PushID(picker_type);
45899f464c52Smaya            ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
45909f464c52Smaya            if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
45919f464c52Smaya            if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
45929f464c52Smaya            ImVec2 backup_pos = GetCursorScreenPos();
45939f464c52Smaya            if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
45949f464c52Smaya                g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
45959f464c52Smaya            SetCursorScreenPos(backup_pos);
45969f464c52Smaya            ImVec4 dummy_ref_col;
45979f464c52Smaya            memcpy(&dummy_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
45989f464c52Smaya            ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
45999f464c52Smaya            PopID();
46009f464c52Smaya        }
46019f464c52Smaya        PopItemWidth();
46029f464c52Smaya    }
46039f464c52Smaya    if (allow_opt_alpha_bar)
46049f464c52Smaya    {
46059f464c52Smaya        if (allow_opt_picker) Separator();
46069f464c52Smaya        CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
46079f464c52Smaya    }
46089f464c52Smaya    EndPopup();
46099f464c52Smaya}
46109f464c52Smaya
46119f464c52Smaya//-------------------------------------------------------------------------
46129f464c52Smaya// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
46139f464c52Smaya//-------------------------------------------------------------------------
46149f464c52Smaya// - TreeNode()
46159f464c52Smaya// - TreeNodeV()
46169f464c52Smaya// - TreeNodeEx()
46179f464c52Smaya// - TreeNodeExV()
46189f464c52Smaya// - TreeNodeBehavior() [Internal]
46199f464c52Smaya// - TreePush()
46209f464c52Smaya// - TreePop()
46219f464c52Smaya// - TreeAdvanceToLabelPos()
46229f464c52Smaya// - GetTreeNodeToLabelSpacing()
46239f464c52Smaya// - SetNextTreeNodeOpen()
46249f464c52Smaya// - CollapsingHeader()
46259f464c52Smaya//-------------------------------------------------------------------------
46269f464c52Smaya
46279f464c52Smayabool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
46289f464c52Smaya{
46299f464c52Smaya    va_list args;
46309f464c52Smaya    va_start(args, fmt);
46319f464c52Smaya    bool is_open = TreeNodeExV(str_id, 0, fmt, args);
46329f464c52Smaya    va_end(args);
46339f464c52Smaya    return is_open;
46349f464c52Smaya}
46359f464c52Smaya
46369f464c52Smayabool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
46379f464c52Smaya{
46389f464c52Smaya    va_list args;
46399f464c52Smaya    va_start(args, fmt);
46409f464c52Smaya    bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
46419f464c52Smaya    va_end(args);
46429f464c52Smaya    return is_open;
46439f464c52Smaya}
46449f464c52Smaya
46459f464c52Smayabool ImGui::TreeNode(const char* label)
46469f464c52Smaya{
46479f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
46489f464c52Smaya    if (window->SkipItems)
46499f464c52Smaya        return false;
46509f464c52Smaya    return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
46519f464c52Smaya}
46529f464c52Smaya
46539f464c52Smayabool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
46549f464c52Smaya{
46559f464c52Smaya    return TreeNodeExV(str_id, 0, fmt, args);
46569f464c52Smaya}
46579f464c52Smaya
46589f464c52Smayabool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
46599f464c52Smaya{
46609f464c52Smaya    return TreeNodeExV(ptr_id, 0, fmt, args);
46619f464c52Smaya}
46629f464c52Smaya
46639f464c52Smayabool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
46649f464c52Smaya{
46659f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
46669f464c52Smaya    if (window->SkipItems)
46679f464c52Smaya        return false;
46689f464c52Smaya
46699f464c52Smaya    return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
46709f464c52Smaya}
46719f464c52Smaya
46729f464c52Smayabool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
46739f464c52Smaya{
46749f464c52Smaya    va_list args;
46759f464c52Smaya    va_start(args, fmt);
46769f464c52Smaya    bool is_open = TreeNodeExV(str_id, flags, fmt, args);
46779f464c52Smaya    va_end(args);
46789f464c52Smaya    return is_open;
46799f464c52Smaya}
46809f464c52Smaya
46819f464c52Smayabool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
46829f464c52Smaya{
46839f464c52Smaya    va_list args;
46849f464c52Smaya    va_start(args, fmt);
46859f464c52Smaya    bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
46869f464c52Smaya    va_end(args);
46879f464c52Smaya    return is_open;
46889f464c52Smaya}
46899f464c52Smaya
46909f464c52Smayabool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
46919f464c52Smaya{
46929f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
46939f464c52Smaya    if (window->SkipItems)
46949f464c52Smaya        return false;
46959f464c52Smaya
46969f464c52Smaya    ImGuiContext& g = *GImGui;
46979f464c52Smaya    const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
46989f464c52Smaya    return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
46999f464c52Smaya}
47009f464c52Smaya
47019f464c52Smayabool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
47029f464c52Smaya{
47039f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
47049f464c52Smaya    if (window->SkipItems)
47059f464c52Smaya        return false;
47069f464c52Smaya
47079f464c52Smaya    ImGuiContext& g = *GImGui;
47089f464c52Smaya    const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
47099f464c52Smaya    return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
47109f464c52Smaya}
47119f464c52Smaya
47129f464c52Smayabool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
47139f464c52Smaya{
47149f464c52Smaya    if (flags & ImGuiTreeNodeFlags_Leaf)
47159f464c52Smaya        return true;
47169f464c52Smaya
47179f464c52Smaya    // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
47189f464c52Smaya    ImGuiContext& g = *GImGui;
47199f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
47209f464c52Smaya    ImGuiStorage* storage = window->DC.StateStorage;
47219f464c52Smaya
47229f464c52Smaya    bool is_open;
47239f464c52Smaya    if (g.NextTreeNodeOpenCond != 0)
47249f464c52Smaya    {
47259f464c52Smaya        if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
47269f464c52Smaya        {
47279f464c52Smaya            is_open = g.NextTreeNodeOpenVal;
47289f464c52Smaya            storage->SetInt(id, is_open);
47299f464c52Smaya        }
47309f464c52Smaya        else
47319f464c52Smaya        {
47329f464c52Smaya            // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
47339f464c52Smaya            const int stored_value = storage->GetInt(id, -1);
47349f464c52Smaya            if (stored_value == -1)
47359f464c52Smaya            {
47369f464c52Smaya                is_open = g.NextTreeNodeOpenVal;
47379f464c52Smaya                storage->SetInt(id, is_open);
47389f464c52Smaya            }
47399f464c52Smaya            else
47409f464c52Smaya            {
47419f464c52Smaya                is_open = stored_value != 0;
47429f464c52Smaya            }
47439f464c52Smaya        }
47449f464c52Smaya        g.NextTreeNodeOpenCond = 0;
47459f464c52Smaya    }
47469f464c52Smaya    else
47479f464c52Smaya    {
47489f464c52Smaya        is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
47499f464c52Smaya    }
47509f464c52Smaya
47519f464c52Smaya    // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
47529f464c52Smaya    // NB- If we are above max depth we still allow manually opened nodes to be logged.
47539f464c52Smaya    if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
47549f464c52Smaya        is_open = true;
47559f464c52Smaya
47569f464c52Smaya    return is_open;
47579f464c52Smaya}
47589f464c52Smaya
47599f464c52Smayabool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
47609f464c52Smaya{
47619f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
47629f464c52Smaya    if (window->SkipItems)
47639f464c52Smaya        return false;
47649f464c52Smaya
47659f464c52Smaya    ImGuiContext& g = *GImGui;
47669f464c52Smaya    const ImGuiStyle& style = g.Style;
47679f464c52Smaya    const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
47689f464c52Smaya    const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
47699f464c52Smaya
47709f464c52Smaya    if (!label_end)
47719f464c52Smaya        label_end = FindRenderedTextEnd(label);
47729f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, label_end, false);
47739f464c52Smaya
47749f464c52Smaya    // We vertically grow up to current line height up the typical widget height.
47759f464c52Smaya    const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
47769f464c52Smaya    const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
47779f464c52Smaya    ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
47789f464c52Smaya    if (display_frame)
47799f464c52Smaya    {
47809f464c52Smaya        // Framed header expand a little outside the default padding
47819f464c52Smaya        frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
47829f464c52Smaya        frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
47839f464c52Smaya    }
47849f464c52Smaya
47859f464c52Smaya    const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2));   // Collapser arrow width + Spacing
47869f464c52Smaya    const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f);   // Include collapser
47879f464c52Smaya    ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
47889f464c52Smaya
47899f464c52Smaya    // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
47909f464c52Smaya    // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
47919f464c52Smaya    const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
47929f464c52Smaya    bool is_open = TreeNodeBehaviorIsOpen(id, flags);
47939f464c52Smaya    bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
47949f464c52Smaya
47959f464c52Smaya    // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
47969f464c52Smaya    // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
47979f464c52Smaya    // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
47989f464c52Smaya    if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
47999f464c52Smaya        window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
48009f464c52Smaya
48019f464c52Smaya    bool item_add = ItemAdd(interact_bb, id);
48029f464c52Smaya    window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
48039f464c52Smaya    window->DC.LastItemDisplayRect = frame_bb;
48049f464c52Smaya
48059f464c52Smaya    if (!item_add)
48069f464c52Smaya    {
48079f464c52Smaya        if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
48089f464c52Smaya            TreePushRawID(id);
48099f464c52Smaya        IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
48109f464c52Smaya        return is_open;
48119f464c52Smaya    }
48129f464c52Smaya
48139f464c52Smaya    // Flags that affects opening behavior:
48149f464c52Smaya    // - 0 (default) .................... single-click anywhere to open
48159f464c52Smaya    // - OpenOnDoubleClick .............. double-click anywhere to open
48169f464c52Smaya    // - OpenOnArrow .................... single-click on arrow to open
48179f464c52Smaya    // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
48189f464c52Smaya    ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers;
48199f464c52Smaya    if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
48209f464c52Smaya        button_flags |= ImGuiButtonFlags_AllowItemOverlap;
48219f464c52Smaya    if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
48229f464c52Smaya        button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
48239f464c52Smaya    if (!is_leaf)
48249f464c52Smaya        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
48259f464c52Smaya
48269f464c52Smaya    bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
48279f464c52Smaya    bool hovered, held;
48289f464c52Smaya    bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
48299f464c52Smaya    bool toggled = false;
48309f464c52Smaya    if (!is_leaf)
48319f464c52Smaya    {
48329f464c52Smaya        if (pressed)
48339f464c52Smaya        {
48349f464c52Smaya            toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
48359f464c52Smaya            if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
48369f464c52Smaya                toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
48379f464c52Smaya            if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
48389f464c52Smaya                toggled |= g.IO.MouseDoubleClicked[0];
48399f464c52Smaya            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.
48409f464c52Smaya                toggled = false;
48419f464c52Smaya        }
48429f464c52Smaya
48439f464c52Smaya        if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
48449f464c52Smaya        {
48459f464c52Smaya            toggled = true;
48469f464c52Smaya            NavMoveRequestCancel();
48479f464c52Smaya        }
48489f464c52Smaya        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?
48499f464c52Smaya        {
48509f464c52Smaya            toggled = true;
48519f464c52Smaya            NavMoveRequestCancel();
48529f464c52Smaya        }
48539f464c52Smaya
48549f464c52Smaya        if (toggled)
48559f464c52Smaya        {
48569f464c52Smaya            is_open = !is_open;
48579f464c52Smaya            window->DC.StateStorage->SetInt(id, is_open);
48589f464c52Smaya        }
48599f464c52Smaya    }
48609f464c52Smaya    if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
48619f464c52Smaya        SetItemAllowOverlap();
48629f464c52Smaya
48639f464c52Smaya    // Render
48649f464c52Smaya    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
48659f464c52Smaya    const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
48669f464c52Smaya    ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
48679f464c52Smaya    if (display_frame)
48689f464c52Smaya    {
48699f464c52Smaya        // Framed type
48709f464c52Smaya        RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
48719f464c52Smaya        RenderNavHighlight(frame_bb, id, nav_highlight_flags);
48729f464c52Smaya        RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
48739f464c52Smaya        if (g.LogEnabled)
48749f464c52Smaya        {
48759f464c52Smaya            // 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.
48769f464c52Smaya            const char log_prefix[] = "\n##";
48779f464c52Smaya            const char log_suffix[] = "##";
48789f464c52Smaya            LogRenderedText(&text_pos, log_prefix, log_prefix+3);
48799f464c52Smaya            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
48809f464c52Smaya            LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
48819f464c52Smaya        }
48829f464c52Smaya        else
48839f464c52Smaya        {
48849f464c52Smaya            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
48859f464c52Smaya        }
48869f464c52Smaya    }
48879f464c52Smaya    else
48889f464c52Smaya    {
48899f464c52Smaya        // Unframed typed for tree nodes
48909f464c52Smaya        if (hovered || selected)
48919f464c52Smaya        {
48929f464c52Smaya            RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
48939f464c52Smaya            RenderNavHighlight(frame_bb, id, nav_highlight_flags);
48949f464c52Smaya        }
48959f464c52Smaya
48969f464c52Smaya        if (flags & ImGuiTreeNodeFlags_Bullet)
48979f464c52Smaya            RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
48989f464c52Smaya        else if (!is_leaf)
48999f464c52Smaya            RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
49009f464c52Smaya        if (g.LogEnabled)
49019f464c52Smaya            LogRenderedText(&text_pos, ">");
49029f464c52Smaya        RenderText(text_pos, label, label_end, false);
49039f464c52Smaya    }
49049f464c52Smaya
49059f464c52Smaya    if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
49069f464c52Smaya        TreePushRawID(id);
49079f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
49089f464c52Smaya    return is_open;
49099f464c52Smaya}
49109f464c52Smaya
49119f464c52Smayavoid ImGui::TreePush(const char* str_id)
49129f464c52Smaya{
49139f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
49149f464c52Smaya    Indent();
49159f464c52Smaya    window->DC.TreeDepth++;
49169f464c52Smaya    PushID(str_id ? str_id : "#TreePush");
49179f464c52Smaya}
49189f464c52Smaya
49199f464c52Smayavoid ImGui::TreePush(const void* ptr_id)
49209f464c52Smaya{
49219f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
49229f464c52Smaya    Indent();
49239f464c52Smaya    window->DC.TreeDepth++;
49249f464c52Smaya    PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
49259f464c52Smaya}
49269f464c52Smaya
49279f464c52Smayavoid ImGui::TreePushRawID(ImGuiID id)
49289f464c52Smaya{
49299f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
49309f464c52Smaya    Indent();
49319f464c52Smaya    window->DC.TreeDepth++;
49329f464c52Smaya    window->IDStack.push_back(id);
49339f464c52Smaya}
49349f464c52Smaya
49359f464c52Smayavoid ImGui::TreePop()
49369f464c52Smaya{
49379f464c52Smaya    ImGuiContext& g = *GImGui;
49389f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
49399f464c52Smaya    Unindent();
49409f464c52Smaya
49419f464c52Smaya    window->DC.TreeDepth--;
49429f464c52Smaya    if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
49439f464c52Smaya        if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
49449f464c52Smaya        {
49459f464c52Smaya            SetNavID(window->IDStack.back(), g.NavLayer);
49469f464c52Smaya            NavMoveRequestCancel();
49479f464c52Smaya        }
49489f464c52Smaya    window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
49499f464c52Smaya
49509f464c52Smaya    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.
49519f464c52Smaya    PopID();
49529f464c52Smaya}
49539f464c52Smaya
49549f464c52Smayavoid ImGui::TreeAdvanceToLabelPos()
49559f464c52Smaya{
49569f464c52Smaya    ImGuiContext& g = *GImGui;
49579f464c52Smaya    g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
49589f464c52Smaya}
49599f464c52Smaya
49609f464c52Smaya// Horizontal distance preceding label when using TreeNode() or Bullet()
49619f464c52Smayafloat ImGui::GetTreeNodeToLabelSpacing()
49629f464c52Smaya{
49639f464c52Smaya    ImGuiContext& g = *GImGui;
49649f464c52Smaya    return g.FontSize + (g.Style.FramePadding.x * 2.0f);
49659f464c52Smaya}
49669f464c52Smaya
49679f464c52Smayavoid ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
49689f464c52Smaya{
49699f464c52Smaya    ImGuiContext& g = *GImGui;
49709f464c52Smaya    if (g.CurrentWindow->SkipItems)
49719f464c52Smaya        return;
49729f464c52Smaya    g.NextTreeNodeOpenVal = is_open;
49739f464c52Smaya    g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
49749f464c52Smaya}
49759f464c52Smaya
49769f464c52Smaya// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
49779f464c52Smaya// 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().
49789f464c52Smayabool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
49799f464c52Smaya{
49809f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
49819f464c52Smaya    if (window->SkipItems)
49829f464c52Smaya        return false;
49839f464c52Smaya
49849f464c52Smaya    return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
49859f464c52Smaya}
49869f464c52Smaya
49879f464c52Smayabool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
49889f464c52Smaya{
49899f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
49909f464c52Smaya    if (window->SkipItems)
49919f464c52Smaya        return false;
49929f464c52Smaya
49939f464c52Smaya    if (p_open && !*p_open)
49949f464c52Smaya        return false;
49959f464c52Smaya
49969f464c52Smaya    ImGuiID id = window->GetID(label);
49979f464c52Smaya    bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
49989f464c52Smaya    if (p_open)
49999f464c52Smaya    {
50009f464c52Smaya        // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
50019f464c52Smaya        ImGuiContext& g = *GImGui;
50029f464c52Smaya        ImGuiItemHoveredDataBackup last_item_backup;
50039f464c52Smaya        float button_radius = g.FontSize * 0.5f;
50049f464c52Smaya        ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);
50059f464c52Smaya        if (CloseButton(window->GetID((void*)((intptr_t)id+1)), button_center, button_radius))
50069f464c52Smaya            *p_open = false;
50079f464c52Smaya        last_item_backup.Restore();
50089f464c52Smaya    }
50099f464c52Smaya
50109f464c52Smaya    return is_open;
50119f464c52Smaya}
50129f464c52Smaya
50139f464c52Smaya//-------------------------------------------------------------------------
50149f464c52Smaya// [SECTION] Widgets: Selectable
50159f464c52Smaya//-------------------------------------------------------------------------
50169f464c52Smaya// - Selectable()
50179f464c52Smaya//-------------------------------------------------------------------------
50189f464c52Smaya
50199f464c52Smaya// Tip: pass a non-visible label (e.g. "##dummy") then you can use the space to draw other text or image.
50209f464c52Smaya// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
50219f464c52Smayabool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
50229f464c52Smaya{
50239f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
50249f464c52Smaya    if (window->SkipItems)
50259f464c52Smaya        return false;
50269f464c52Smaya
50279f464c52Smaya    ImGuiContext& g = *GImGui;
50289f464c52Smaya    const ImGuiStyle& style = g.Style;
50299f464c52Smaya
50309f464c52Smaya    if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
50319f464c52Smaya        PopClipRect();
50329f464c52Smaya
50339f464c52Smaya    ImGuiID id = window->GetID(label);
50349f464c52Smaya    ImVec2 label_size = CalcTextSize(label, NULL, true);
50359f464c52Smaya    ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
50369f464c52Smaya    ImVec2 pos = window->DC.CursorPos;
50379f464c52Smaya    pos.y += window->DC.CurrentLineTextBaseOffset;
50389f464c52Smaya    ImRect bb_inner(pos, pos + size);
50399f464c52Smaya    ItemSize(bb_inner);
50409f464c52Smaya
50419f464c52Smaya    // Fill horizontal space.
50429f464c52Smaya    ImVec2 window_padding = window->WindowPadding;
50439f464c52Smaya    float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
50449f464c52Smaya    float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - pos.x);
50459f464c52Smaya    ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
50469f464c52Smaya    ImRect bb(pos, pos + size_draw);
50479f464c52Smaya    if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
50489f464c52Smaya        bb.Max.x += window_padding.x;
50499f464c52Smaya
50509f464c52Smaya    // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
50519f464c52Smaya    float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
50529f464c52Smaya    float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
50539f464c52Smaya    float spacing_R = style.ItemSpacing.x - spacing_L;
50549f464c52Smaya    float spacing_D = style.ItemSpacing.y - spacing_U;
50559f464c52Smaya    bb.Min.x -= spacing_L;
50569f464c52Smaya    bb.Min.y -= spacing_U;
50579f464c52Smaya    bb.Max.x += spacing_R;
50589f464c52Smaya    bb.Max.y += spacing_D;
50599f464c52Smaya    if (!ItemAdd(bb, id))
50609f464c52Smaya    {
50619f464c52Smaya        if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
50629f464c52Smaya            PushColumnClipRect();
50639f464c52Smaya        return false;
50649f464c52Smaya    }
50659f464c52Smaya
50669f464c52Smaya    // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
50679f464c52Smaya    ImGuiButtonFlags button_flags = 0;
50689f464c52Smaya    if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;
50699f464c52Smaya    if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
50709f464c52Smaya    if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
50719f464c52Smaya    if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
50729f464c52Smaya    if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
50739f464c52Smaya    if (flags & ImGuiSelectableFlags_Disabled)
50749f464c52Smaya        selected = false;
50759f464c52Smaya
50769f464c52Smaya    bool hovered, held;
50779f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
50789f464c52Smaya    // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
50799f464c52Smaya    if (pressed || hovered)
50809f464c52Smaya        if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
50819f464c52Smaya        {
50829f464c52Smaya            g.NavDisableHighlight = true;
50839f464c52Smaya            SetNavID(id, window->DC.NavLayerCurrent);
50849f464c52Smaya        }
50859f464c52Smaya    if (pressed)
50869f464c52Smaya        MarkItemEdited(id);
50879f464c52Smaya
50889f464c52Smaya    // Render
50899f464c52Smaya    if (hovered || selected)
50909f464c52Smaya    {
50919f464c52Smaya        const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
50929f464c52Smaya        RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
50939f464c52Smaya        RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
50949f464c52Smaya    }
50959f464c52Smaya
50969f464c52Smaya    if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
50979f464c52Smaya    {
50989f464c52Smaya        PushColumnClipRect();
50999f464c52Smaya        bb.Max.x -= (GetContentRegionMax().x - max_x);
51009f464c52Smaya    }
51019f464c52Smaya
51029f464c52Smaya    if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
51039f464c52Smaya    RenderTextClipped(bb_inner.Min, bb_inner.Max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
51049f464c52Smaya    if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
51059f464c52Smaya
51069f464c52Smaya    // Automatically close popups
51079f464c52Smaya    if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
51089f464c52Smaya        CloseCurrentPopup();
51099f464c52Smaya    return pressed;
51109f464c52Smaya}
51119f464c52Smaya
51129f464c52Smayabool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
51139f464c52Smaya{
51149f464c52Smaya    if (Selectable(label, *p_selected, flags, size_arg))
51159f464c52Smaya    {
51169f464c52Smaya        *p_selected = !*p_selected;
51179f464c52Smaya        return true;
51189f464c52Smaya    }
51199f464c52Smaya    return false;
51209f464c52Smaya}
51219f464c52Smaya
51229f464c52Smaya//-------------------------------------------------------------------------
51239f464c52Smaya// [SECTION] Widgets: ListBox
51249f464c52Smaya//-------------------------------------------------------------------------
51259f464c52Smaya// - ListBox()
51269f464c52Smaya// - ListBoxHeader()
51279f464c52Smaya// - ListBoxFooter()
51289f464c52Smaya//-------------------------------------------------------------------------
51299f464c52Smaya
51309f464c52Smaya// FIXME: In principle this function should be called BeginListBox(). We should rename it after re-evaluating if we want to keep the same signature.
51319f464c52Smaya// Helper to calculate the size of a listbox and display a label on the right.
51329f464c52Smaya// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
51339f464c52Smayabool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
51349f464c52Smaya{
51359f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
51369f464c52Smaya    if (window->SkipItems)
51379f464c52Smaya        return false;
51389f464c52Smaya
51399f464c52Smaya    const ImGuiStyle& style = GetStyle();
51409f464c52Smaya    const ImGuiID id = GetID(label);
51419f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
51429f464c52Smaya
51439f464c52Smaya    // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
51449f464c52Smaya    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
51459f464c52Smaya    ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
51469f464c52Smaya    ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
51479f464c52Smaya    ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
51489f464c52Smaya    window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
51499f464c52Smaya
51509f464c52Smaya    if (!IsRectVisible(bb.Min, bb.Max))
51519f464c52Smaya    {
51529f464c52Smaya        ItemSize(bb.GetSize(), style.FramePadding.y);
51539f464c52Smaya        ItemAdd(bb, 0, &frame_bb);
51549f464c52Smaya        return false;
51559f464c52Smaya    }
51569f464c52Smaya
51579f464c52Smaya    BeginGroup();
51589f464c52Smaya    if (label_size.x > 0)
51599f464c52Smaya        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
51609f464c52Smaya
51619f464c52Smaya    BeginChildFrame(id, frame_bb.GetSize());
51629f464c52Smaya    return true;
51639f464c52Smaya}
51649f464c52Smaya
51659f464c52Smaya// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
51669f464c52Smayabool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
51679f464c52Smaya{
51689f464c52Smaya    // Size default to hold ~7.25 items.
51699f464c52Smaya    // 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.
51709f464c52Smaya    // 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.
51719f464c52Smaya    // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
51729f464c52Smaya    if (height_in_items < 0)
51739f464c52Smaya        height_in_items = ImMin(items_count, 7);
51749f464c52Smaya    const ImGuiStyle& style = GetStyle();
51759f464c52Smaya    float height_in_items_f = (height_in_items < items_count) ? (height_in_items + 0.25f) : (height_in_items + 0.00f);
51769f464c52Smaya
51779f464c52Smaya    // 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().
51789f464c52Smaya    ImVec2 size;
51799f464c52Smaya    size.x = 0.0f;
51809f464c52Smaya    size.y = GetTextLineHeightWithSpacing() * height_in_items_f + style.FramePadding.y * 2.0f;
51819f464c52Smaya    return ListBoxHeader(label, size);
51829f464c52Smaya}
51839f464c52Smaya
51849f464c52Smaya// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
51859f464c52Smayavoid ImGui::ListBoxFooter()
51869f464c52Smaya{
51879f464c52Smaya    ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
51889f464c52Smaya    const ImRect bb = parent_window->DC.LastItemRect;
51899f464c52Smaya    const ImGuiStyle& style = GetStyle();
51909f464c52Smaya
51919f464c52Smaya    EndChildFrame();
51929f464c52Smaya
51939f464c52Smaya    // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
51949f464c52Smaya    // We call SameLine() to restore DC.CurrentLine* data
51959f464c52Smaya    SameLine();
51969f464c52Smaya    parent_window->DC.CursorPos = bb.Min;
51979f464c52Smaya    ItemSize(bb, style.FramePadding.y);
51989f464c52Smaya    EndGroup();
51999f464c52Smaya}
52009f464c52Smaya
52019f464c52Smayabool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
52029f464c52Smaya{
52039f464c52Smaya    const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
52049f464c52Smaya    return value_changed;
52059f464c52Smaya}
52069f464c52Smaya
52079f464c52Smayabool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
52089f464c52Smaya{
52099f464c52Smaya    if (!ListBoxHeader(label, items_count, height_in_items))
52109f464c52Smaya        return false;
52119f464c52Smaya
52129f464c52Smaya    // 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.
52139f464c52Smaya    ImGuiContext& g = *GImGui;
52149f464c52Smaya    bool value_changed = false;
52159f464c52Smaya    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.
52169f464c52Smaya    while (clipper.Step())
52179f464c52Smaya        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
52189f464c52Smaya        {
52199f464c52Smaya            const bool item_selected = (i == *current_item);
52209f464c52Smaya            const char* item_text;
52219f464c52Smaya            if (!items_getter(data, i, &item_text))
52229f464c52Smaya                item_text = "*Unknown item*";
52239f464c52Smaya
52249f464c52Smaya            PushID(i);
52259f464c52Smaya            if (Selectable(item_text, item_selected))
52269f464c52Smaya            {
52279f464c52Smaya                *current_item = i;
52289f464c52Smaya                value_changed = true;
52299f464c52Smaya            }
52309f464c52Smaya            if (item_selected)
52319f464c52Smaya                SetItemDefaultFocus();
52329f464c52Smaya            PopID();
52339f464c52Smaya        }
52349f464c52Smaya    ListBoxFooter();
52359f464c52Smaya    if (value_changed)
52369f464c52Smaya        MarkItemEdited(g.CurrentWindow->DC.LastItemId);
52379f464c52Smaya
52389f464c52Smaya    return value_changed;
52399f464c52Smaya}
52409f464c52Smaya
52419f464c52Smaya//-------------------------------------------------------------------------
52429f464c52Smaya// [SECTION] Widgets: PlotLines, PlotHistogram
52439f464c52Smaya//-------------------------------------------------------------------------
52449f464c52Smaya// - PlotEx() [Internal]
52459f464c52Smaya// - PlotLines()
52469f464c52Smaya// - PlotHistogram()
52479f464c52Smaya//-------------------------------------------------------------------------
52489f464c52Smaya
52499f464c52Smayavoid 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)
52509f464c52Smaya{
52519f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
52529f464c52Smaya    if (window->SkipItems)
52539f464c52Smaya        return;
52549f464c52Smaya
52559f464c52Smaya    ImGuiContext& g = *GImGui;
52569f464c52Smaya    const ImGuiStyle& style = g.Style;
52579f464c52Smaya    const ImGuiID id = window->GetID(label);
52589f464c52Smaya
52599f464c52Smaya    const ImVec2 label_size = CalcTextSize(label, NULL, true);
52609f464c52Smaya    if (frame_size.x == 0.0f)
52619f464c52Smaya        frame_size.x = CalcItemWidth();
52629f464c52Smaya    if (frame_size.y == 0.0f)
52639f464c52Smaya        frame_size.y = label_size.y + (style.FramePadding.y * 2);
52649f464c52Smaya
52659f464c52Smaya    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
52669f464c52Smaya    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
52679f464c52Smaya    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));
52689f464c52Smaya    ItemSize(total_bb, style.FramePadding.y);
52699f464c52Smaya    if (!ItemAdd(total_bb, 0, &frame_bb))
52709f464c52Smaya        return;
52719f464c52Smaya    const bool hovered = ItemHoverable(frame_bb, id);
52729f464c52Smaya
52739f464c52Smaya    // Determine scale from values if not specified
52749f464c52Smaya    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
52759f464c52Smaya    {
52769f464c52Smaya        float v_min = FLT_MAX;
52779f464c52Smaya        float v_max = -FLT_MAX;
52789f464c52Smaya        for (int i = 0; i < values_count; i++)
52799f464c52Smaya        {
52809f464c52Smaya            const float v = values_getter(data, i);
52819f464c52Smaya            v_min = ImMin(v_min, v);
52829f464c52Smaya            v_max = ImMax(v_max, v);
52839f464c52Smaya        }
52849f464c52Smaya        if (scale_min == FLT_MAX)
52859f464c52Smaya            scale_min = v_min;
52869f464c52Smaya        if (scale_max == FLT_MAX)
52879f464c52Smaya            scale_max = v_max;
52889f464c52Smaya    }
52899f464c52Smaya
52909f464c52Smaya    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
52919f464c52Smaya
52929f464c52Smaya    if (values_count > 0)
52939f464c52Smaya    {
52949f464c52Smaya        int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
52959f464c52Smaya        int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
52969f464c52Smaya
52979f464c52Smaya        // Tooltip on hover
52989f464c52Smaya        int v_hovered = -1;
52999f464c52Smaya        if (hovered && inner_bb.Contains(g.IO.MousePos))
53009f464c52Smaya        {
53019f464c52Smaya            const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
53029f464c52Smaya            const int v_idx = (int)(t * item_count);
53039f464c52Smaya            IM_ASSERT(v_idx >= 0 && v_idx < values_count);
53049f464c52Smaya
53059f464c52Smaya            const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
53069f464c52Smaya            const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
53079f464c52Smaya            if (plot_type == ImGuiPlotType_Lines)
53089f464c52Smaya                SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
53099f464c52Smaya            else if (plot_type == ImGuiPlotType_Histogram)
53109f464c52Smaya                SetTooltip("%d: %8.4g", v_idx, v0);
53119f464c52Smaya            v_hovered = v_idx;
53129f464c52Smaya        }
53139f464c52Smaya
53149f464c52Smaya        const float t_step = 1.0f / (float)res_w;
53159f464c52Smaya        const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
53169f464c52Smaya
53179f464c52Smaya        float v0 = values_getter(data, (0 + values_offset) % values_count);
53189f464c52Smaya        float t0 = 0.0f;
53199f464c52Smaya        ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
53209f464c52Smaya        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
53219f464c52Smaya
53229f464c52Smaya        const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
53239f464c52Smaya        const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
53249f464c52Smaya
53259f464c52Smaya        for (int n = 0; n < res_w; n++)
53269f464c52Smaya        {
53279f464c52Smaya            const float t1 = t0 + t_step;
53289f464c52Smaya            const int v1_idx = (int)(t0 * item_count + 0.5f);
53299f464c52Smaya            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
53309f464c52Smaya            const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
53319f464c52Smaya            const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
53329f464c52Smaya
53339f464c52Smaya            // 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.
53349f464c52Smaya            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
53359f464c52Smaya            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
53369f464c52Smaya            if (plot_type == ImGuiPlotType_Lines)
53379f464c52Smaya            {
53389f464c52Smaya                window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
53399f464c52Smaya            }
53409f464c52Smaya            else if (plot_type == ImGuiPlotType_Histogram)
53419f464c52Smaya            {
53429f464c52Smaya                if (pos1.x >= pos0.x + 2.0f)
53439f464c52Smaya                    pos1.x -= 1.0f;
53449f464c52Smaya                window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
53459f464c52Smaya            }
53469f464c52Smaya
53479f464c52Smaya            t0 = t1;
53489f464c52Smaya            tp0 = tp1;
53499f464c52Smaya        }
53509f464c52Smaya    }
53519f464c52Smaya
53529f464c52Smaya    // Text overlay
53539f464c52Smaya    if (overlay_text)
53549f464c52Smaya        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));
53559f464c52Smaya
53569f464c52Smaya    if (label_size.x > 0.0f)
53579f464c52Smaya        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
53589f464c52Smaya}
53599f464c52Smaya
53609f464c52Smayastruct ImGuiPlotArrayGetterData
53619f464c52Smaya{
53629f464c52Smaya    const float* Values;
53639f464c52Smaya    int Stride;
53649f464c52Smaya
53659f464c52Smaya    ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
53669f464c52Smaya};
53679f464c52Smaya
53689f464c52Smayastatic float Plot_ArrayGetter(void* data, int idx)
53699f464c52Smaya{
53709f464c52Smaya    ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
53719f464c52Smaya    const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
53729f464c52Smaya    return v;
53739f464c52Smaya}
53749f464c52Smaya
53759f464c52Smayavoid 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)
53769f464c52Smaya{
53779f464c52Smaya    ImGuiPlotArrayGetterData data(values, stride);
53789f464c52Smaya    PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
53799f464c52Smaya}
53809f464c52Smaya
53819f464c52Smayavoid 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)
53829f464c52Smaya{
53839f464c52Smaya    PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
53849f464c52Smaya}
53859f464c52Smaya
53869f464c52Smayavoid 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)
53879f464c52Smaya{
53889f464c52Smaya    ImGuiPlotArrayGetterData data(values, stride);
53899f464c52Smaya    PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
53909f464c52Smaya}
53919f464c52Smaya
53929f464c52Smayavoid 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)
53939f464c52Smaya{
53949f464c52Smaya    PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
53959f464c52Smaya}
53969f464c52Smaya
53979f464c52Smaya//-------------------------------------------------------------------------
53989f464c52Smaya// [SECTION] Widgets: Value helpers
53999f464c52Smaya// Those is not very useful, legacy API.
54009f464c52Smaya//-------------------------------------------------------------------------
54019f464c52Smaya// - Value()
54029f464c52Smaya//-------------------------------------------------------------------------
54039f464c52Smaya
54049f464c52Smayavoid ImGui::Value(const char* prefix, bool b)
54059f464c52Smaya{
54069f464c52Smaya    Text("%s: %s", prefix, (b ? "true" : "false"));
54079f464c52Smaya}
54089f464c52Smaya
54099f464c52Smayavoid ImGui::Value(const char* prefix, int v)
54109f464c52Smaya{
54119f464c52Smaya    Text("%s: %d", prefix, v);
54129f464c52Smaya}
54139f464c52Smaya
54149f464c52Smayavoid ImGui::Value(const char* prefix, unsigned int v)
54159f464c52Smaya{
54169f464c52Smaya    Text("%s: %d", prefix, v);
54179f464c52Smaya}
54189f464c52Smaya
54199f464c52Smayavoid ImGui::Value(const char* prefix, float v, const char* float_format)
54209f464c52Smaya{
54219f464c52Smaya    if (float_format)
54229f464c52Smaya    {
54239f464c52Smaya        char fmt[64];
54249f464c52Smaya        ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
54259f464c52Smaya        Text(fmt, prefix, v);
54269f464c52Smaya    }
54279f464c52Smaya    else
54289f464c52Smaya    {
54299f464c52Smaya        Text("%s: %.3f", prefix, v);
54309f464c52Smaya    }
54319f464c52Smaya}
54329f464c52Smaya
54339f464c52Smaya//-------------------------------------------------------------------------
54349f464c52Smaya// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
54359f464c52Smaya//-------------------------------------------------------------------------
54369f464c52Smaya// - ImGuiMenuColumns [Internal]
54379f464c52Smaya// - BeginMainMenuBar()
54389f464c52Smaya// - EndMainMenuBar()
54399f464c52Smaya// - BeginMenuBar()
54409f464c52Smaya// - EndMenuBar()
54419f464c52Smaya// - BeginMenu()
54429f464c52Smaya// - EndMenu()
54439f464c52Smaya// - MenuItem()
54449f464c52Smaya//-------------------------------------------------------------------------
54459f464c52Smaya
54469f464c52Smaya// Helpers for internal use
54479f464c52SmayaImGuiMenuColumns::ImGuiMenuColumns()
54489f464c52Smaya{
54499f464c52Smaya    Count = 0;
54509f464c52Smaya    Spacing = Width = NextWidth = 0.0f;
54519f464c52Smaya    memset(Pos, 0, sizeof(Pos));
54529f464c52Smaya    memset(NextWidths, 0, sizeof(NextWidths));
54539f464c52Smaya}
54549f464c52Smaya
54559f464c52Smayavoid ImGuiMenuColumns::Update(int count, float spacing, bool clear)
54569f464c52Smaya{
54579f464c52Smaya    IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
54589f464c52Smaya    Count = count;
54599f464c52Smaya    Width = NextWidth = 0.0f;
54609f464c52Smaya    Spacing = spacing;
54619f464c52Smaya    if (clear) memset(NextWidths, 0, sizeof(NextWidths));
54629f464c52Smaya    for (int i = 0; i < Count; i++)
54639f464c52Smaya    {
54649f464c52Smaya        if (i > 0 && NextWidths[i] > 0.0f)
54659f464c52Smaya            Width += Spacing;
54669f464c52Smaya        Pos[i] = (float)(int)Width;
54679f464c52Smaya        Width += NextWidths[i];
54689f464c52Smaya        NextWidths[i] = 0.0f;
54699f464c52Smaya    }
54709f464c52Smaya}
54719f464c52Smaya
54729f464c52Smayafloat ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
54739f464c52Smaya{
54749f464c52Smaya    NextWidth = 0.0f;
54759f464c52Smaya    NextWidths[0] = ImMax(NextWidths[0], w0);
54769f464c52Smaya    NextWidths[1] = ImMax(NextWidths[1], w1);
54779f464c52Smaya    NextWidths[2] = ImMax(NextWidths[2], w2);
54789f464c52Smaya    for (int i = 0; i < 3; i++)
54799f464c52Smaya        NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
54809f464c52Smaya    return ImMax(Width, NextWidth);
54819f464c52Smaya}
54829f464c52Smaya
54839f464c52Smayafloat ImGuiMenuColumns::CalcExtraSpace(float avail_w)
54849f464c52Smaya{
54859f464c52Smaya    return ImMax(0.0f, avail_w - Width);
54869f464c52Smaya}
54879f464c52Smaya
54889f464c52Smaya// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
54899f464c52Smayabool ImGui::BeginMainMenuBar()
54909f464c52Smaya{
54919f464c52Smaya    ImGuiContext& g = *GImGui;
54929f464c52Smaya    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
54939f464c52Smaya    SetNextWindowPos(ImVec2(0.0f, 0.0f));
54949f464c52Smaya    SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
54959f464c52Smaya    PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
54969f464c52Smaya    PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
54979f464c52Smaya    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
54989f464c52Smaya    bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
54999f464c52Smaya    PopStyleVar(2);
55009f464c52Smaya    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
55019f464c52Smaya    if (!is_open)
55029f464c52Smaya    {
55039f464c52Smaya        End();
55049f464c52Smaya        return false;
55059f464c52Smaya    }
55069f464c52Smaya    return true; //-V1020
55079f464c52Smaya}
55089f464c52Smaya
55099f464c52Smayavoid ImGui::EndMainMenuBar()
55109f464c52Smaya{
55119f464c52Smaya    EndMenuBar();
55129f464c52Smaya
55139f464c52Smaya    // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
55149f464c52Smaya    ImGuiContext& g = *GImGui;
55159f464c52Smaya    if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
55169f464c52Smaya        FocusPreviousWindowIgnoringOne(g.NavWindow);
55179f464c52Smaya
55189f464c52Smaya    End();
55199f464c52Smaya}
55209f464c52Smaya
55219f464c52Smayabool ImGui::BeginMenuBar()
55229f464c52Smaya{
55239f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
55249f464c52Smaya    if (window->SkipItems)
55259f464c52Smaya        return false;
55269f464c52Smaya    if (!(window->Flags & ImGuiWindowFlags_MenuBar))
55279f464c52Smaya        return false;
55289f464c52Smaya
55299f464c52Smaya    IM_ASSERT(!window->DC.MenuBarAppending);
55309f464c52Smaya    BeginGroup(); // Backup position on layer 0
55319f464c52Smaya    PushID("##menubar");
55329f464c52Smaya
55339f464c52Smaya    // 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.
55349f464c52Smaya    // 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.
55359f464c52Smaya    ImRect bar_rect = window->MenuBarRect();
55369f464c52Smaya    ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
55379f464c52Smaya    clip_rect.ClipWith(window->OuterRectClipped);
55389f464c52Smaya    PushClipRect(clip_rect.Min, clip_rect.Max, false);
55399f464c52Smaya
55409f464c52Smaya    window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
55419f464c52Smaya    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
55429f464c52Smaya    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
55439f464c52Smaya    window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);
55449f464c52Smaya    window->DC.MenuBarAppending = true;
55459f464c52Smaya    AlignTextToFramePadding();
55469f464c52Smaya    return true;
55479f464c52Smaya}
55489f464c52Smaya
55499f464c52Smayavoid ImGui::EndMenuBar()
55509f464c52Smaya{
55519f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
55529f464c52Smaya    if (window->SkipItems)
55539f464c52Smaya        return;
55549f464c52Smaya    ImGuiContext& g = *GImGui;
55559f464c52Smaya
55569f464c52Smaya    // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
55579f464c52Smaya    if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
55589f464c52Smaya    {
55599f464c52Smaya        ImGuiWindow* nav_earliest_child = g.NavWindow;
55609f464c52Smaya        while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
55619f464c52Smaya            nav_earliest_child = nav_earliest_child->ParentWindow;
55629f464c52Smaya        if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
55639f464c52Smaya        {
55649f464c52Smaya            // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
55659f464c52Smaya            // 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)
55669f464c52Smaya            IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
55679f464c52Smaya            FocusWindow(window);
55689f464c52Smaya            SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
55699f464c52Smaya            g.NavLayer = ImGuiNavLayer_Menu;
55709f464c52Smaya            g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
55719f464c52Smaya            g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
55729f464c52Smaya            NavMoveRequestCancel();
55739f464c52Smaya        }
55749f464c52Smaya    }
55759f464c52Smaya
55769f464c52Smaya    IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
55779f464c52Smaya    IM_ASSERT(window->DC.MenuBarAppending);
55789f464c52Smaya    PopClipRect();
55799f464c52Smaya    PopID();
55809f464c52Smaya    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.
55819f464c52Smaya    window->DC.GroupStack.back().AdvanceCursor = false;
55829f464c52Smaya    EndGroup(); // Restore position on layer 0
55839f464c52Smaya    window->DC.LayoutType = ImGuiLayoutType_Vertical;
55849f464c52Smaya    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
55859f464c52Smaya    window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
55869f464c52Smaya    window->DC.MenuBarAppending = false;
55879f464c52Smaya}
55889f464c52Smaya
55899f464c52Smayabool ImGui::BeginMenu(const char* label, bool enabled)
55909f464c52Smaya{
55919f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
55929f464c52Smaya    if (window->SkipItems)
55939f464c52Smaya        return false;
55949f464c52Smaya
55959f464c52Smaya    ImGuiContext& g = *GImGui;
55969f464c52Smaya    const ImGuiStyle& style = g.Style;
55979f464c52Smaya    const ImGuiID id = window->GetID(label);
55989f464c52Smaya
55999f464c52Smaya    ImVec2 label_size = CalcTextSize(label, NULL, true);
56009f464c52Smaya
56019f464c52Smaya    bool pressed;
56029f464c52Smaya    bool menu_is_open = IsPopupOpen(id);
56039f464c52Smaya    bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
56049f464c52Smaya    ImGuiWindow* backed_nav_window = g.NavWindow;
56059f464c52Smaya    if (menuset_is_open)
56069f464c52Smaya        g.NavWindow = window;  // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
56079f464c52Smaya
56089f464c52Smaya    // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
56099f464c52Smaya    // However the final position is going to be different! It is choosen by FindBestWindowPosForPopup().
56109f464c52Smaya    // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
56119f464c52Smaya    ImVec2 popup_pos, pos = window->DC.CursorPos;
56129f464c52Smaya    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
56139f464c52Smaya    {
56149f464c52Smaya        // Menu inside an horizontal menu bar
56159f464c52Smaya        // Selectable extend their highlight by half ItemSpacing in each direction.
56169f464c52Smaya        // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
56179f464c52Smaya        popup_pos = ImVec2(pos.x - 1.0f - (float)(int)(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
56189f464c52Smaya        window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
56199f464c52Smaya        PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
56209f464c52Smaya        float w = label_size.x;
56219f464c52Smaya        pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
56229f464c52Smaya        PopStyleVar();
56239f464c52Smaya        window->DC.CursorPos.x += (float)(int)(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().
56249f464c52Smaya    }
56259f464c52Smaya    else
56269f464c52Smaya    {
56279f464c52Smaya        // Menu inside a menu
56289f464c52Smaya        popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
56299f464c52Smaya        float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
56309f464c52Smaya        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
56319f464c52Smaya        pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
56329f464c52Smaya        if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
56339f464c52Smaya        RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
56349f464c52Smaya        if (!enabled) PopStyleColor();
56359f464c52Smaya    }
56369f464c52Smaya
56379f464c52Smaya    const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
56389f464c52Smaya    if (menuset_is_open)
56399f464c52Smaya        g.NavWindow = backed_nav_window;
56409f464c52Smaya
56419f464c52Smaya    bool want_open = false, want_close = false;
56429f464c52Smaya    if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
56439f464c52Smaya    {
56449f464c52Smaya        // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
56459f464c52Smaya        bool moving_within_opened_triangle = false;
56469f464c52Smaya        if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
56479f464c52Smaya        {
56489f464c52Smaya            if (ImGuiWindow* next_window = g.OpenPopupStack[g.BeginPopupStack.Size].Window)
56499f464c52Smaya            {
56509f464c52Smaya                // FIXME-DPI: Values should be derived from a master "scale" factor.
56519f464c52Smaya                ImRect next_window_rect = next_window->Rect();
56529f464c52Smaya                ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
56539f464c52Smaya                ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
56549f464c52Smaya                ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
56559f464c52Smaya                float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
56569f464c52Smaya                ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f;    // to avoid numerical issues
56579f464c52Smaya                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?
56589f464c52Smaya                tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
56599f464c52Smaya                moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
56609f464c52Smaya                //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
56619f464c52Smaya            }
56629f464c52Smaya        }
56639f464c52Smaya
56649f464c52Smaya        want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
56659f464c52Smaya        want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
56669f464c52Smaya
56679f464c52Smaya        if (g.NavActivateId == id)
56689f464c52Smaya        {
56699f464c52Smaya            want_close = menu_is_open;
56709f464c52Smaya            want_open = !menu_is_open;
56719f464c52Smaya        }
56729f464c52Smaya        if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
56739f464c52Smaya        {
56749f464c52Smaya            want_open = true;
56759f464c52Smaya            NavMoveRequestCancel();
56769f464c52Smaya        }
56779f464c52Smaya    }
56789f464c52Smaya    else
56799f464c52Smaya    {
56809f464c52Smaya        // Menu bar
56819f464c52Smaya        if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
56829f464c52Smaya        {
56839f464c52Smaya            want_close = true;
56849f464c52Smaya            want_open = menu_is_open = false;
56859f464c52Smaya        }
56869f464c52Smaya        else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
56879f464c52Smaya        {
56889f464c52Smaya            want_open = true;
56899f464c52Smaya        }
56909f464c52Smaya        else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
56919f464c52Smaya        {
56929f464c52Smaya            want_open = true;
56939f464c52Smaya            NavMoveRequestCancel();
56949f464c52Smaya        }
56959f464c52Smaya    }
56969f464c52Smaya
56979f464c52Smaya    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.. }'
56989f464c52Smaya        want_close = true;
56999f464c52Smaya    if (want_close && IsPopupOpen(id))
57009f464c52Smaya        ClosePopupToLevel(g.BeginPopupStack.Size, true);
57019f464c52Smaya
57029f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
57039f464c52Smaya
57049f464c52Smaya    if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
57059f464c52Smaya    {
57069f464c52Smaya        // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
57079f464c52Smaya        OpenPopup(label);
57089f464c52Smaya        return false;
57099f464c52Smaya    }
57109f464c52Smaya
57119f464c52Smaya    menu_is_open |= want_open;
57129f464c52Smaya    if (want_open)
57139f464c52Smaya        OpenPopup(label);
57149f464c52Smaya
57159f464c52Smaya    if (menu_is_open)
57169f464c52Smaya    {
57179f464c52Smaya        // 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)
57189f464c52Smaya        SetNextWindowPos(popup_pos, ImGuiCond_Always);
57199f464c52Smaya        ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
57209f464c52Smaya        if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
57219f464c52Smaya            flags |= ImGuiWindowFlags_ChildWindow;
57229f464c52Smaya        menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
57239f464c52Smaya    }
57249f464c52Smaya
57259f464c52Smaya    return menu_is_open;
57269f464c52Smaya}
57279f464c52Smaya
57289f464c52Smayavoid ImGui::EndMenu()
57299f464c52Smaya{
57309f464c52Smaya    // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
57319f464c52Smaya    // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
57329f464c52Smaya    // 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.
57339f464c52Smaya    ImGuiContext& g = *GImGui;
57349f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
57359f464c52Smaya    if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
57369f464c52Smaya    {
57379f464c52Smaya        ClosePopupToLevel(g.BeginPopupStack.Size, true);
57389f464c52Smaya        NavMoveRequestCancel();
57399f464c52Smaya    }
57409f464c52Smaya
57419f464c52Smaya    EndPopup();
57429f464c52Smaya}
57439f464c52Smaya
57449f464c52Smayabool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
57459f464c52Smaya{
57469f464c52Smaya    ImGuiWindow* window = GetCurrentWindow();
57479f464c52Smaya    if (window->SkipItems)
57489f464c52Smaya        return false;
57499f464c52Smaya
57509f464c52Smaya    ImGuiContext& g = *GImGui;
57519f464c52Smaya    ImGuiStyle& style = g.Style;
57529f464c52Smaya    ImVec2 pos = window->DC.CursorPos;
57539f464c52Smaya    ImVec2 label_size = CalcTextSize(label, NULL, true);
57549f464c52Smaya
57559f464c52Smaya    ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
57569f464c52Smaya    bool pressed;
57579f464c52Smaya    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
57589f464c52Smaya    {
57599f464c52Smaya        // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
57609f464c52Smaya        // Note that in this situation we render neither the shortcut neither the selected tick mark
57619f464c52Smaya        float w = label_size.x;
57629f464c52Smaya        window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
57639f464c52Smaya        PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
57649f464c52Smaya        pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
57659f464c52Smaya        PopStyleVar();
57669f464c52Smaya        window->DC.CursorPos.x += (float)(int)(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().
57679f464c52Smaya    }
57689f464c52Smaya    else
57699f464c52Smaya    {
57709f464c52Smaya        ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
57719f464c52Smaya        float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
57729f464c52Smaya        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
57739f464c52Smaya        pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
57749f464c52Smaya        if (shortcut_size.x > 0.0f)
57759f464c52Smaya        {
57769f464c52Smaya            PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
57779f464c52Smaya            RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
57789f464c52Smaya            PopStyleColor();
57799f464c52Smaya        }
57809f464c52Smaya        if (selected)
57819f464c52Smaya            RenderCheckMark(pos + ImVec2(window->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);
57829f464c52Smaya    }
57839f464c52Smaya
57849f464c52Smaya    IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
57859f464c52Smaya    return pressed;
57869f464c52Smaya}
57879f464c52Smaya
57889f464c52Smayabool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
57899f464c52Smaya{
57909f464c52Smaya    if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
57919f464c52Smaya    {
57929f464c52Smaya        if (p_selected)
57939f464c52Smaya            *p_selected = !*p_selected;
57949f464c52Smaya        return true;
57959f464c52Smaya    }
57969f464c52Smaya    return false;
57979f464c52Smaya}
57989f464c52Smaya
57999f464c52Smaya//-------------------------------------------------------------------------
58009f464c52Smaya// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
58019f464c52Smaya//-------------------------------------------------------------------------
58029f464c52Smaya// [BETA API] API may evolve! This code has been extracted out of the Docking branch,
58039f464c52Smaya// and some of the construct which are not used in Master may be left here to facilitate merging.
58049f464c52Smaya//-------------------------------------------------------------------------
58059f464c52Smaya// - BeginTabBar()
58069f464c52Smaya// - BeginTabBarEx() [Internal]
58079f464c52Smaya// - EndTabBar()
58089f464c52Smaya// - TabBarLayout() [Internal]
58099f464c52Smaya// - TabBarCalcTabID() [Internal]
58109f464c52Smaya// - TabBarCalcMaxTabWidth() [Internal]
58119f464c52Smaya// - TabBarFindTabById() [Internal]
58129f464c52Smaya// - TabBarRemoveTab() [Internal]
58139f464c52Smaya// - TabBarCloseTab() [Internal]
58149f464c52Smaya// - TabBarScrollClamp()v
58159f464c52Smaya// - TabBarScrollToTab() [Internal]
58169f464c52Smaya// - TabBarQueueChangeTabOrder() [Internal]
58179f464c52Smaya// - TabBarScrollingButtons() [Internal]
58189f464c52Smaya// - TabBarTabListPopupButton() [Internal]
58199f464c52Smaya//-------------------------------------------------------------------------
58209f464c52Smaya
58219f464c52Smayanamespace ImGui
58229f464c52Smaya{
58239f464c52Smaya    static void             TabBarLayout(ImGuiTabBar* tab_bar);
58249f464c52Smaya    static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
58259f464c52Smaya    static float            TabBarCalcMaxTabWidth();
58269f464c52Smaya    static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
58279f464c52Smaya    static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
58289f464c52Smaya    static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
58299f464c52Smaya    static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
58309f464c52Smaya}
58319f464c52Smaya
58329f464c52SmayaImGuiTabBar::ImGuiTabBar()
58339f464c52Smaya{
58349f464c52Smaya    ID = 0;
58359f464c52Smaya    SelectedTabId = NextSelectedTabId = VisibleTabId = 0;
58369f464c52Smaya    CurrFrameVisible = PrevFrameVisible = -1;
58379f464c52Smaya    ContentsHeight = 0.0f;
58389f464c52Smaya    OffsetMax = OffsetNextTab = 0.0f;
58399f464c52Smaya    ScrollingAnim = ScrollingTarget = 0.0f;
58409f464c52Smaya    Flags = ImGuiTabBarFlags_None;
58419f464c52Smaya    ReorderRequestTabId = 0;
58429f464c52Smaya    ReorderRequestDir = 0;
58439f464c52Smaya    WantLayout = VisibleTabWasSubmitted = false;
58449f464c52Smaya    LastTabItemIdx = -1;
58459f464c52Smaya}
58469f464c52Smaya
58479f464c52Smayastatic int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const void* rhs)
58489f464c52Smaya{
58499f464c52Smaya    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
58509f464c52Smaya    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
58519f464c52Smaya    return (int)(a->Offset - b->Offset);
58529f464c52Smaya}
58539f464c52Smaya
58549f464c52Smayastatic int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs)
58559f464c52Smaya{
58569f464c52Smaya    const ImGuiTabBarSortItem* a = (const ImGuiTabBarSortItem*)lhs;
58579f464c52Smaya    const ImGuiTabBarSortItem* b = (const ImGuiTabBarSortItem*)rhs;
58589f464c52Smaya    if (int d = (int)(b->Width - a->Width))
58599f464c52Smaya        return d;
58609f464c52Smaya    return (b->Index - a->Index);
58619f464c52Smaya}
58629f464c52Smaya
58639f464c52Smayabool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
58649f464c52Smaya{
58659f464c52Smaya    ImGuiContext& g = *GImGui;
58669f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
58679f464c52Smaya    if (window->SkipItems)
58689f464c52Smaya        return false;
58699f464c52Smaya
58709f464c52Smaya    ImGuiID id = window->GetID(str_id);
58719f464c52Smaya    ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
58729f464c52Smaya    ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->InnerClipRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
58739f464c52Smaya    tab_bar->ID = id;
58749f464c52Smaya    return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
58759f464c52Smaya}
58769f464c52Smaya
58779f464c52Smayabool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
58789f464c52Smaya{
58799f464c52Smaya    ImGuiContext& g = *GImGui;
58809f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
58819f464c52Smaya    if (window->SkipItems)
58829f464c52Smaya        return false;
58839f464c52Smaya
58849f464c52Smaya    if ((flags & ImGuiTabBarFlags_DockNode) == 0)
58859f464c52Smaya        window->IDStack.push_back(tab_bar->ID);
58869f464c52Smaya
58879f464c52Smaya    g.CurrentTabBar.push_back(tab_bar);
58889f464c52Smaya    if (tab_bar->CurrFrameVisible == g.FrameCount)
58899f464c52Smaya    {
58909f464c52Smaya        //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
58919f464c52Smaya        IM_ASSERT(0);
58929f464c52Smaya        return true;
58939f464c52Smaya    }
58949f464c52Smaya
58959f464c52Smaya    // When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order.
58969f464c52Smaya    // 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.
58979f464c52Smaya    if ((flags & ImGuiTabBarFlags_Reorderable) && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1 && tab_bar->PrevFrameVisible != -1)
58989f464c52Smaya        ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByVisibleOffset);
58999f464c52Smaya
59009f464c52Smaya    // Flags
59019f464c52Smaya    if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
59029f464c52Smaya        flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
59039f464c52Smaya
59049f464c52Smaya    tab_bar->Flags = flags;
59059f464c52Smaya    tab_bar->BarRect = tab_bar_bb;
59069f464c52Smaya    tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
59079f464c52Smaya    tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
59089f464c52Smaya    tab_bar->CurrFrameVisible = g.FrameCount;
59099f464c52Smaya    tab_bar->FramePadding = g.Style.FramePadding;
59109f464c52Smaya
59119f464c52Smaya    // Layout
59129f464c52Smaya    ItemSize(ImVec2(tab_bar->OffsetMax, tab_bar->BarRect.GetHeight()));
59139f464c52Smaya    window->DC.CursorPos.x = tab_bar->BarRect.Min.x;
59149f464c52Smaya
59159f464c52Smaya    // Draw separator
59169f464c52Smaya    const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_Tab);
59179f464c52Smaya    const float y = tab_bar->BarRect.Max.y - 1.0f;
59189f464c52Smaya    {
59199f464c52Smaya        const float separator_min_x = tab_bar->BarRect.Min.x - window->WindowPadding.x;
59209f464c52Smaya        const float separator_max_x = tab_bar->BarRect.Max.x + window->WindowPadding.x;
59219f464c52Smaya        window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
59229f464c52Smaya    }
59239f464c52Smaya    return true;
59249f464c52Smaya}
59259f464c52Smaya
59269f464c52Smayavoid    ImGui::EndTabBar()
59279f464c52Smaya{
59289f464c52Smaya    ImGuiContext& g = *GImGui;
59299f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
59309f464c52Smaya    if (window->SkipItems)
59319f464c52Smaya        return;
59329f464c52Smaya
59339f464c52Smaya    IM_ASSERT(!g.CurrentTabBar.empty());      // Mismatched BeginTabBar/EndTabBar
59349f464c52Smaya    ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
59359f464c52Smaya    if (tab_bar->WantLayout)
59369f464c52Smaya        TabBarLayout(tab_bar);
59379f464c52Smaya
59389f464c52Smaya    // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
59399f464c52Smaya    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
59409f464c52Smaya    if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
59419f464c52Smaya        tab_bar->ContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, 0.0f);
59429f464c52Smaya    else
59439f464c52Smaya        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->ContentsHeight;
59449f464c52Smaya
59459f464c52Smaya    if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
59469f464c52Smaya        PopID();
59479f464c52Smaya    g.CurrentTabBar.pop_back();
59489f464c52Smaya}
59499f464c52Smaya
59509f464c52Smaya// This is called only once a frame before by the first call to ItemTab()
59519f464c52Smaya// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
59529f464c52Smayastatic void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
59539f464c52Smaya{
59549f464c52Smaya    ImGuiContext& g = *GImGui;
59559f464c52Smaya    tab_bar->WantLayout = false;
59569f464c52Smaya
59579f464c52Smaya    // Garbage collect
59589f464c52Smaya    int tab_dst_n = 0;
59599f464c52Smaya    for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
59609f464c52Smaya    {
59619f464c52Smaya        ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
59629f464c52Smaya        if (tab->LastFrameVisible < tab_bar->PrevFrameVisible)
59639f464c52Smaya        {
59649f464c52Smaya            if (tab->ID == tab_bar->SelectedTabId)
59659f464c52Smaya                tab_bar->SelectedTabId = 0;
59669f464c52Smaya            continue;
59679f464c52Smaya        }
59689f464c52Smaya        if (tab_dst_n != tab_src_n)
59699f464c52Smaya            tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
59709f464c52Smaya        tab_dst_n++;
59719f464c52Smaya    }
59729f464c52Smaya    if (tab_bar->Tabs.Size != tab_dst_n)
59739f464c52Smaya        tab_bar->Tabs.resize(tab_dst_n);
59749f464c52Smaya
59759f464c52Smaya    // Setup next selected tab
59769f464c52Smaya    ImGuiID scroll_track_selected_tab_id = 0;
59779f464c52Smaya    if (tab_bar->NextSelectedTabId)
59789f464c52Smaya    {
59799f464c52Smaya        tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
59809f464c52Smaya        tab_bar->NextSelectedTabId = 0;
59819f464c52Smaya        scroll_track_selected_tab_id = tab_bar->SelectedTabId;
59829f464c52Smaya    }
59839f464c52Smaya
59849f464c52Smaya    // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
59859f464c52Smaya    if (tab_bar->ReorderRequestTabId != 0)
59869f464c52Smaya    {
59879f464c52Smaya        if (ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId))
59889f464c52Smaya        {
59899f464c52Smaya            //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
59909f464c52Smaya            int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir;
59919f464c52Smaya            if (tab2_order >= 0 && tab2_order < tab_bar->Tabs.Size)
59929f464c52Smaya            {
59939f464c52Smaya                ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
59949f464c52Smaya                ImGuiTabItem item_tmp = *tab1;
59959f464c52Smaya                *tab1 = *tab2;
59969f464c52Smaya                *tab2 = item_tmp;
59979f464c52Smaya                if (tab2->ID == tab_bar->SelectedTabId)
59989f464c52Smaya                    scroll_track_selected_tab_id = tab2->ID;
59999f464c52Smaya                tab1 = tab2 = NULL;
60009f464c52Smaya            }
60019f464c52Smaya            if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
60029f464c52Smaya                MarkIniSettingsDirty();
60039f464c52Smaya        }
60049f464c52Smaya        tab_bar->ReorderRequestTabId = 0;
60059f464c52Smaya    }
60069f464c52Smaya
60079f464c52Smaya    // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
60089f464c52Smaya    const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
60099f464c52Smaya    if (tab_list_popup_button)
60109f464c52Smaya        if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Max.x!
60119f464c52Smaya            scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
60129f464c52Smaya
60139f464c52Smaya    ImVector<ImGuiTabBarSortItem>& width_sort_buffer = g.TabSortByWidthBuffer;
60149f464c52Smaya    width_sort_buffer.resize(tab_bar->Tabs.Size);
60159f464c52Smaya
60169f464c52Smaya    // Compute ideal widths
60179f464c52Smaya    float width_total_contents = 0.0f;
60189f464c52Smaya    ImGuiTabItem* most_recently_selected_tab = NULL;
60199f464c52Smaya    bool found_selected_tab_id = false;
60209f464c52Smaya    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
60219f464c52Smaya    {
60229f464c52Smaya        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
60239f464c52Smaya        IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
60249f464c52Smaya
60259f464c52Smaya        if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
60269f464c52Smaya            most_recently_selected_tab = tab;
60279f464c52Smaya        if (tab->ID == tab_bar->SelectedTabId)
60289f464c52Smaya            found_selected_tab_id = true;
60299f464c52Smaya
60309f464c52Smaya        // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
60319f464c52Smaya        // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
60329f464c52Smaya        // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
60339f464c52Smaya        const char* tab_name = tab_bar->GetTabName(tab);
60349f464c52Smaya        tab->WidthContents = TabItemCalcSize(tab_name, (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true).x;
60359f464c52Smaya
60369f464c52Smaya        width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->WidthContents;
60379f464c52Smaya
60389f464c52Smaya        // Store data so we can build an array sorted by width if we need to shrink tabs down
60399f464c52Smaya        width_sort_buffer[tab_n].Index = tab_n;
60409f464c52Smaya        width_sort_buffer[tab_n].Width = tab->WidthContents;
60419f464c52Smaya    }
60429f464c52Smaya
60439f464c52Smaya    // Compute width
60449f464c52Smaya    const float width_avail = tab_bar->BarRect.GetWidth();
60459f464c52Smaya    float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f;
60469f464c52Smaya    if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown))
60479f464c52Smaya    {
60489f464c52Smaya        // If we don't have enough room, resize down the largest tabs first
60499f464c52Smaya        if (tab_bar->Tabs.Size > 1)
60509f464c52Smaya            ImQsort(width_sort_buffer.Data, (size_t)width_sort_buffer.Size, sizeof(ImGuiTabBarSortItem), TabBarSortItemComparer);
60519f464c52Smaya        int tab_count_same_width = 1;
60529f464c52Smaya        while (width_excess > 0.0f && tab_count_same_width < tab_bar->Tabs.Size)
60539f464c52Smaya        {
60549f464c52Smaya            while (tab_count_same_width < tab_bar->Tabs.Size && width_sort_buffer[0].Width == width_sort_buffer[tab_count_same_width].Width)
60559f464c52Smaya                tab_count_same_width++;
60569f464c52Smaya            float width_to_remove_per_tab_max = (tab_count_same_width < tab_bar->Tabs.Size) ? (width_sort_buffer[0].Width - width_sort_buffer[tab_count_same_width].Width) : (width_sort_buffer[0].Width - 1.0f);
60579f464c52Smaya            float width_to_remove_per_tab = ImMin(width_excess / tab_count_same_width, width_to_remove_per_tab_max);
60589f464c52Smaya            for (int tab_n = 0; tab_n < tab_count_same_width; tab_n++)
60599f464c52Smaya                width_sort_buffer[tab_n].Width -= width_to_remove_per_tab;
60609f464c52Smaya            width_excess -= width_to_remove_per_tab * tab_count_same_width;
60619f464c52Smaya        }
60629f464c52Smaya        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
60639f464c52Smaya            tab_bar->Tabs[width_sort_buffer[tab_n].Index].Width = (float)(int)width_sort_buffer[tab_n].Width;
60649f464c52Smaya    }
60659f464c52Smaya    else
60669f464c52Smaya    {
60679f464c52Smaya        const float tab_max_width = TabBarCalcMaxTabWidth();
60689f464c52Smaya        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
60699f464c52Smaya        {
60709f464c52Smaya            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
60719f464c52Smaya            tab->Width = ImMin(tab->WidthContents, tab_max_width);
60729f464c52Smaya        }
60739f464c52Smaya    }
60749f464c52Smaya
60759f464c52Smaya    // Layout all active tabs
60769f464c52Smaya    float offset_x = 0.0f;
60779f464c52Smaya    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
60789f464c52Smaya    {
60799f464c52Smaya        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
60809f464c52Smaya        tab->Offset = offset_x;
60819f464c52Smaya        if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID)
60829f464c52Smaya            scroll_track_selected_tab_id = tab->ID;
60839f464c52Smaya        offset_x += tab->Width + g.Style.ItemInnerSpacing.x;
60849f464c52Smaya    }
60859f464c52Smaya    tab_bar->OffsetMax = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f);
60869f464c52Smaya    tab_bar->OffsetNextTab = 0.0f;
60879f464c52Smaya
60889f464c52Smaya    // Horizontal scrolling buttons
60899f464c52Smaya    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);
60909f464c52Smaya    if (scrolling_buttons)
60919f464c52Smaya        if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x!
60929f464c52Smaya            scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
60939f464c52Smaya
60949f464c52Smaya    // If we have lost the selected tab, select the next most recently active one
60959f464c52Smaya    if (found_selected_tab_id == false)
60969f464c52Smaya        tab_bar->SelectedTabId = 0;
60979f464c52Smaya    if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
60989f464c52Smaya        scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
60999f464c52Smaya
61009f464c52Smaya    // Lock in visible tab
61019f464c52Smaya    tab_bar->VisibleTabId = tab_bar->SelectedTabId;
61029f464c52Smaya    tab_bar->VisibleTabWasSubmitted = false;
61039f464c52Smaya
61049f464c52Smaya    // Update scrolling
61059f464c52Smaya    if (scroll_track_selected_tab_id)
61069f464c52Smaya        if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id))
61079f464c52Smaya            TabBarScrollToTab(tab_bar, scroll_track_selected_tab);
61089f464c52Smaya    tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
61099f464c52Smaya    tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
61109f464c52Smaya    const float scrolling_speed = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) ? FLT_MAX : (g.IO.DeltaTime * g.FontSize * 70.0f);
61119f464c52Smaya    if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
61129f464c52Smaya        tab_bar->ScrollingAnim = ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, scrolling_speed);
61139f464c52Smaya
61149f464c52Smaya    // Clear name buffers
61159f464c52Smaya    if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
61169f464c52Smaya        tab_bar->TabsNames.Buf.resize(0);
61179f464c52Smaya}
61189f464c52Smaya
61199f464c52Smaya// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
61209f464c52Smayastatic ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
61219f464c52Smaya{
61229f464c52Smaya    if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
61239f464c52Smaya    {
61249f464c52Smaya        ImGuiID id = ImHashStr(label, 0);
61259f464c52Smaya        KeepAliveID(id);
61269f464c52Smaya        return id;
61279f464c52Smaya    }
61289f464c52Smaya    else
61299f464c52Smaya    {
61309f464c52Smaya        ImGuiWindow* window = GImGui->CurrentWindow;
61319f464c52Smaya        return window->GetID(label);
61329f464c52Smaya    }
61339f464c52Smaya}
61349f464c52Smaya
61359f464c52Smayastatic float ImGui::TabBarCalcMaxTabWidth()
61369f464c52Smaya{
61379f464c52Smaya    ImGuiContext& g = *GImGui;
61389f464c52Smaya    return g.FontSize * 20.0f;
61399f464c52Smaya}
61409f464c52Smaya
61419f464c52SmayaImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
61429f464c52Smaya{
61439f464c52Smaya    if (tab_id != 0)
61449f464c52Smaya        for (int n = 0; n < tab_bar->Tabs.Size; n++)
61459f464c52Smaya            if (tab_bar->Tabs[n].ID == tab_id)
61469f464c52Smaya                return &tab_bar->Tabs[n];
61479f464c52Smaya    return NULL;
61489f464c52Smaya}
61499f464c52Smaya
61509f464c52Smaya// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
61519f464c52Smayavoid ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
61529f464c52Smaya{
61539f464c52Smaya    if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
61549f464c52Smaya        tab_bar->Tabs.erase(tab);
61559f464c52Smaya    if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
61569f464c52Smaya    if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
61579f464c52Smaya    if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
61589f464c52Smaya}
61599f464c52Smaya
61609f464c52Smaya// Called on manual closure attempt
61619f464c52Smayavoid ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
61629f464c52Smaya{
61639f464c52Smaya    if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
61649f464c52Smaya    {
61659f464c52Smaya        // This will remove a frame of lag for selecting another tab on closure.
61669f464c52Smaya        // 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
61679f464c52Smaya        tab->LastFrameVisible = -1;
61689f464c52Smaya        tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
61699f464c52Smaya    }
61709f464c52Smaya    else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
61719f464c52Smaya    {
61729f464c52Smaya        // Actually select before expecting closure
61739f464c52Smaya        tab_bar->NextSelectedTabId = tab->ID;
61749f464c52Smaya    }
61759f464c52Smaya}
61769f464c52Smaya
61779f464c52Smayastatic float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
61789f464c52Smaya{
61799f464c52Smaya    scrolling = ImMin(scrolling, tab_bar->OffsetMax - tab_bar->BarRect.GetWidth());
61809f464c52Smaya    return ImMax(scrolling, 0.0f);
61819f464c52Smaya}
61829f464c52Smaya
61839f464c52Smayastatic void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
61849f464c52Smaya{
61859f464c52Smaya    ImGuiContext& g = *GImGui;
61869f464c52Smaya    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)
61879f464c52Smaya    int order = tab_bar->GetTabOrder(tab);
61889f464c52Smaya    float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f);
61899f464c52Smaya    float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f);
61909f464c52Smaya    if (tab_bar->ScrollingTarget > tab_x1)
61919f464c52Smaya        tab_bar->ScrollingTarget = tab_x1;
61929f464c52Smaya    if (tab_bar->ScrollingTarget + tab_bar->BarRect.GetWidth() < tab_x2)
61939f464c52Smaya        tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth();
61949f464c52Smaya}
61959f464c52Smaya
61969f464c52Smayavoid ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir)
61979f464c52Smaya{
61989f464c52Smaya    IM_ASSERT(dir == -1 || dir == +1);
61999f464c52Smaya    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
62009f464c52Smaya    tab_bar->ReorderRequestTabId = tab->ID;
62019f464c52Smaya    tab_bar->ReorderRequestDir = dir;
62029f464c52Smaya}
62039f464c52Smaya
62049f464c52Smayastatic ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
62059f464c52Smaya{
62069f464c52Smaya    ImGuiContext& g = *GImGui;
62079f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
62089f464c52Smaya
62099f464c52Smaya    const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
62109f464c52Smaya    const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
62119f464c52Smaya
62129f464c52Smaya    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
62139f464c52Smaya    //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));
62149f464c52Smaya
62159f464c52Smaya    const ImRect avail_bar_rect = tab_bar->BarRect;
62169f464c52Smaya    bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f)));
62179f464c52Smaya    if (want_clip_rect)
62189f464c52Smaya        PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true);
62199f464c52Smaya
62209f464c52Smaya    ImGuiTabItem* tab_to_select = NULL;
62219f464c52Smaya
62229f464c52Smaya    int select_dir = 0;
62239f464c52Smaya    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
62249f464c52Smaya    arrow_col.w *= 0.5f;
62259f464c52Smaya
62269f464c52Smaya    PushStyleColor(ImGuiCol_Text, arrow_col);
62279f464c52Smaya    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
62289f464c52Smaya    const float backup_repeat_delay = g.IO.KeyRepeatDelay;
62299f464c52Smaya    const float backup_repeat_rate = g.IO.KeyRepeatRate;
62309f464c52Smaya    g.IO.KeyRepeatDelay = 0.250f;
62319f464c52Smaya    g.IO.KeyRepeatRate = 0.200f;
62329f464c52Smaya    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y);
62339f464c52Smaya    if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
62349f464c52Smaya        select_dir = -1;
62359f464c52Smaya    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y);
62369f464c52Smaya    if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
62379f464c52Smaya        select_dir = +1;
62389f464c52Smaya    PopStyleColor(2);
62399f464c52Smaya    g.IO.KeyRepeatRate = backup_repeat_rate;
62409f464c52Smaya    g.IO.KeyRepeatDelay = backup_repeat_delay;
62419f464c52Smaya
62429f464c52Smaya    if (want_clip_rect)
62439f464c52Smaya        PopClipRect();
62449f464c52Smaya
62459f464c52Smaya    if (select_dir != 0)
62469f464c52Smaya        if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
62479f464c52Smaya        {
62489f464c52Smaya            int selected_order = tab_bar->GetTabOrder(tab_item);
62499f464c52Smaya            int target_order = selected_order + select_dir;
62509f464c52Smaya            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
62519f464c52Smaya        }
62529f464c52Smaya    window->DC.CursorPos = backup_cursor_pos;
62539f464c52Smaya    tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
62549f464c52Smaya
62559f464c52Smaya    return tab_to_select;
62569f464c52Smaya}
62579f464c52Smaya
62589f464c52Smayastatic ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
62599f464c52Smaya{
62609f464c52Smaya    ImGuiContext& g = *GImGui;
62619f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
62629f464c52Smaya
62639f464c52Smaya    // We use g.Style.FramePadding.y to match the square ArrowButton size
62649f464c52Smaya    const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
62659f464c52Smaya    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
62669f464c52Smaya    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
62679f464c52Smaya    tab_bar->BarRect.Min.x += tab_list_popup_button_width;
62689f464c52Smaya
62699f464c52Smaya    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
62709f464c52Smaya    arrow_col.w *= 0.5f;
62719f464c52Smaya    PushStyleColor(ImGuiCol_Text, arrow_col);
62729f464c52Smaya    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
62739f464c52Smaya    bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview);
62749f464c52Smaya    PopStyleColor(2);
62759f464c52Smaya
62769f464c52Smaya    ImGuiTabItem* tab_to_select = NULL;
62779f464c52Smaya    if (open)
62789f464c52Smaya    {
62799f464c52Smaya        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
62809f464c52Smaya        {
62819f464c52Smaya            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
62829f464c52Smaya            const char* tab_name = tab_bar->GetTabName(tab);
62839f464c52Smaya            if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
62849f464c52Smaya                tab_to_select = tab;
62859f464c52Smaya        }
62869f464c52Smaya        EndCombo();
62879f464c52Smaya    }
62889f464c52Smaya
62899f464c52Smaya    window->DC.CursorPos = backup_cursor_pos;
62909f464c52Smaya    return tab_to_select;
62919f464c52Smaya}
62929f464c52Smaya
62939f464c52Smaya//-------------------------------------------------------------------------
62949f464c52Smaya// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
62959f464c52Smaya//-------------------------------------------------------------------------
62969f464c52Smaya// [BETA API] API may evolve! This code has been extracted out of the Docking branch,
62979f464c52Smaya// and some of the construct which are not used in Master may be left here to facilitate merging.
62989f464c52Smaya//-------------------------------------------------------------------------
62999f464c52Smaya// - BeginTabItem()
63009f464c52Smaya// - EndTabItem()
63019f464c52Smaya// - TabItemEx() [Internal]
63029f464c52Smaya// - SetTabItemClosed()
63039f464c52Smaya// - TabItemCalcSize() [Internal]
63049f464c52Smaya// - TabItemBackground() [Internal]
63059f464c52Smaya// - TabItemLabelAndCloseButton() [Internal]
63069f464c52Smaya//-------------------------------------------------------------------------
63079f464c52Smaya
63089f464c52Smayabool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
63099f464c52Smaya{
63109f464c52Smaya    ImGuiContext& g = *GImGui;
63119f464c52Smaya    if (g.CurrentWindow->SkipItems)
63129f464c52Smaya        return false;
63139f464c52Smaya
63149f464c52Smaya    IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
63159f464c52Smaya    ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
63169f464c52Smaya    bool ret = TabItemEx(tab_bar, label, p_open, flags);
63179f464c52Smaya    if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
63189f464c52Smaya    {
63199f464c52Smaya        ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
63209f464c52Smaya        g.CurrentWindow->IDStack.push_back(tab->ID);    // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
63219f464c52Smaya    }
63229f464c52Smaya    return ret;
63239f464c52Smaya}
63249f464c52Smaya
63259f464c52Smayavoid    ImGui::EndTabItem()
63269f464c52Smaya{
63279f464c52Smaya    ImGuiContext& g = *GImGui;
63289f464c52Smaya    if (g.CurrentWindow->SkipItems)
63299f464c52Smaya        return;
63309f464c52Smaya
63319f464c52Smaya    IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
63329f464c52Smaya    ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
63339f464c52Smaya    IM_ASSERT(tab_bar->LastTabItemIdx >= 0 && "Needs to be called between BeginTabItem() and EndTabItem()");
63349f464c52Smaya    ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
63359f464c52Smaya    if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
63369f464c52Smaya        g.CurrentWindow->IDStack.pop_back();
63379f464c52Smaya}
63389f464c52Smaya
63399f464c52Smayabool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
63409f464c52Smaya{
63419f464c52Smaya    // Layout whole tab bar if not already done
63429f464c52Smaya    if (tab_bar->WantLayout)
63439f464c52Smaya        TabBarLayout(tab_bar);
63449f464c52Smaya
63459f464c52Smaya    ImGuiContext& g = *GImGui;
63469f464c52Smaya    ImGuiWindow* window = g.CurrentWindow;
63479f464c52Smaya    if (window->SkipItems)
63489f464c52Smaya        return false;
63499f464c52Smaya
63509f464c52Smaya    const ImGuiStyle& style = g.Style;
63519f464c52Smaya    const ImGuiID id = TabBarCalcTabID(tab_bar, label);
63529f464c52Smaya
63539f464c52Smaya    // 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.
63549f464c52Smaya    if (p_open && !*p_open)
63559f464c52Smaya    {
63569f464c52Smaya        PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
63579f464c52Smaya        ItemAdd(ImRect(), id);
63589f464c52Smaya        PopItemFlag();
63599f464c52Smaya        return false;
63609f464c52Smaya    }
63619f464c52Smaya
63629f464c52Smaya    // Calculate tab contents size
63639f464c52Smaya    ImVec2 size = TabItemCalcSize(label, p_open != NULL);
63649f464c52Smaya
63659f464c52Smaya    // Acquire tab data
63669f464c52Smaya    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
63679f464c52Smaya    bool tab_is_new = false;
63689f464c52Smaya    if (tab == NULL)
63699f464c52Smaya    {
63709f464c52Smaya        tab_bar->Tabs.push_back(ImGuiTabItem());
63719f464c52Smaya        tab = &tab_bar->Tabs.back();
63729f464c52Smaya        tab->ID = id;
63739f464c52Smaya        tab->Width = size.x;
63749f464c52Smaya        tab_is_new = true;
63759f464c52Smaya    }
63769f464c52Smaya    tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab);
63779f464c52Smaya    tab->WidthContents = size.x;
63789f464c52Smaya
63799f464c52Smaya    if (p_open == NULL)
63809f464c52Smaya        flags |= ImGuiTabItemFlags_NoCloseButton;
63819f464c52Smaya
63829f464c52Smaya    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
63839f464c52Smaya    const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
63849f464c52Smaya    const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
63859f464c52Smaya    tab->LastFrameVisible = g.FrameCount;
63869f464c52Smaya    tab->Flags = flags;
63879f464c52Smaya
63889f464c52Smaya    // Append name with zero-terminator
63899f464c52Smaya    tab->NameOffset = tab_bar->TabsNames.size();
63909f464c52Smaya    tab_bar->TabsNames.append(label, label + strlen(label) + 1);
63919f464c52Smaya
63929f464c52Smaya    // If we are not reorderable, always reset offset based on submission order.
63939f464c52Smaya    // (We already handled layout and sizing using the previous known order, but sizing is not affected by order!)
63949f464c52Smaya    if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
63959f464c52Smaya    {
63969f464c52Smaya        tab->Offset = tab_bar->OffsetNextTab;
63979f464c52Smaya        tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x;
63989f464c52Smaya    }
63999f464c52Smaya
64009f464c52Smaya    // Update selected tab
64019f464c52Smaya    if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
64029f464c52Smaya        if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
64039f464c52Smaya            tab_bar->NextSelectedTabId = id;  // New tabs gets activated
64049f464c52Smaya
64059f464c52Smaya    // Lock visibility
64069f464c52Smaya    bool tab_contents_visible = (tab_bar->VisibleTabId == id);
64079f464c52Smaya    if (tab_contents_visible)
64089f464c52Smaya        tab_bar->VisibleTabWasSubmitted = true;
64099f464c52Smaya
64109f464c52Smaya    // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
64119f464c52Smaya    if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
64129f464c52Smaya        if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
64139f464c52Smaya            tab_contents_visible = true;
64149f464c52Smaya
64159f464c52Smaya    if (tab_appearing && !(tab_bar_appearing && !tab_is_new))
64169f464c52Smaya    {
64179f464c52Smaya        PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
64189f464c52Smaya        ItemAdd(ImRect(), id);
64199f464c52Smaya        PopItemFlag();
64209f464c52Smaya        return tab_contents_visible;
64219f464c52Smaya    }
64229f464c52Smaya
64239f464c52Smaya    if (tab_bar->SelectedTabId == id)
64249f464c52Smaya        tab->LastFrameSelected = g.FrameCount;
64259f464c52Smaya
64269f464c52Smaya    // Backup current layout position
64279f464c52Smaya    const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
64289f464c52Smaya
64299f464c52Smaya    // Layout
64309f464c52Smaya    size.x = tab->Width;
64319f464c52Smaya    window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2((float)(int)tab->Offset - tab_bar->ScrollingAnim, 0.0f);
64329f464c52Smaya    ImVec2 pos = window->DC.CursorPos;
64339f464c52Smaya    ImRect bb(pos, pos + size);
64349f464c52Smaya
64359f464c52Smaya    // 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)
64369f464c52Smaya    bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x >= tab_bar->BarRect.Max.x);
64379f464c52Smaya    if (want_clip_rect)
64389f464c52Smaya        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);
64399f464c52Smaya
64409f464c52Smaya    ItemSize(bb, style.FramePadding.y);
64419f464c52Smaya    if (!ItemAdd(bb, id))
64429f464c52Smaya    {
64439f464c52Smaya        if (want_clip_rect)
64449f464c52Smaya            PopClipRect();
64459f464c52Smaya        window->DC.CursorPos = backup_main_cursor_pos;
64469f464c52Smaya        return tab_contents_visible;
64479f464c52Smaya    }
64489f464c52Smaya
64499f464c52Smaya    // Click to Select a tab
64509f464c52Smaya    ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap);
64519f464c52Smaya    if (g.DragDropActive)
64529f464c52Smaya        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
64539f464c52Smaya    bool hovered, held;
64549f464c52Smaya    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
64559f464c52Smaya    hovered |= (g.HoveredId == id);
64569f464c52Smaya    if (pressed || ((flags & ImGuiTabItemFlags_SetSelected) && !tab_contents_visible)) // SetSelected can only be passed on explicit tab bar
64579f464c52Smaya        tab_bar->NextSelectedTabId = id;
64589f464c52Smaya
64599f464c52Smaya    // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
64609f464c52Smaya    if (!held)
64619f464c52Smaya        SetItemAllowOverlap();
64629f464c52Smaya
64639f464c52Smaya    // Drag and drop: re-order tabs
64649f464c52Smaya    if (held && !tab_appearing && IsMouseDragging(0))
64659f464c52Smaya    {
64669f464c52Smaya        if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
64679f464c52Smaya        {
64689f464c52Smaya            // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
64699f464c52Smaya            if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
64709f464c52Smaya            {
64719f464c52Smaya                if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
64729f464c52Smaya                    TabBarQueueChangeTabOrder(tab_bar, tab, -1);
64739f464c52Smaya            }
64749f464c52Smaya            else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
64759f464c52Smaya            {
64769f464c52Smaya                if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
64779f464c52Smaya                    TabBarQueueChangeTabOrder(tab_bar, tab, +1);
64789f464c52Smaya            }
64799f464c52Smaya        }
64809f464c52Smaya    }
64819f464c52Smaya
64829f464c52Smaya#if 0
64839f464c52Smaya    if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->WidthContents)
64849f464c52Smaya    {
64859f464c52Smaya        // Enlarge tab display when hovering
64869f464c52Smaya        bb.Max.x = bb.Min.x + (float)(int)ImLerp(bb.GetWidth(), tab->WidthContents, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f));
64879f464c52Smaya        display_draw_list = GetOverlayDrawList(window);
64889f464c52Smaya        TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
64899f464c52Smaya    }
64909f464c52Smaya#endif
64919f464c52Smaya
64929f464c52Smaya    // Render tab shape
64939f464c52Smaya    ImDrawList* display_draw_list = window->DrawList;
64949f464c52Smaya    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));
64959f464c52Smaya    TabItemBackground(display_draw_list, bb, flags, tab_col);
64969f464c52Smaya    RenderNavHighlight(bb, id);
64979f464c52Smaya
64989f464c52Smaya    // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
64999f464c52Smaya    const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
65009f464c52Smaya    if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
65019f464c52Smaya        tab_bar->NextSelectedTabId = id;
65029f464c52Smaya
65039f464c52Smaya    if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
65049f464c52Smaya        flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
65059f464c52Smaya
65069f464c52Smaya    // Render tab label, process close button
65079f464c52Smaya    const ImGuiID close_button_id = p_open ? window->GetID((void*)((intptr_t)id + 1)) : 0;
65089f464c52Smaya    bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id);
65099f464c52Smaya    if (just_closed && p_open != NULL)
65109f464c52Smaya    {
65119f464c52Smaya        *p_open = false;
65129f464c52Smaya        TabBarCloseTab(tab_bar, tab);
65139f464c52Smaya    }
65149f464c52Smaya
65159f464c52Smaya    // Restore main window position so user can draw there
65169f464c52Smaya    if (want_clip_rect)
65179f464c52Smaya        PopClipRect();
65189f464c52Smaya    window->DC.CursorPos = backup_main_cursor_pos;
65199f464c52Smaya
65209f464c52Smaya    // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
65219f464c52Smaya    if (g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f)
65229f464c52Smaya        if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip))
65239f464c52Smaya            SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
65249f464c52Smaya
65259f464c52Smaya    return tab_contents_visible;
65269f464c52Smaya}
65279f464c52Smaya
65289f464c52Smaya// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
65299f464c52Smaya// To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem()
65309f464c52Smayavoid    ImGui::SetTabItemClosed(const char* label)
65319f464c52Smaya{
65329f464c52Smaya    ImGuiContext& g = *GImGui;
65339f464c52Smaya    bool is_within_manual_tab_bar = (g.CurrentTabBar.Size > 0) && !(g.CurrentTabBar.back()->Flags & ImGuiTabBarFlags_DockNode);
65349f464c52Smaya    if (is_within_manual_tab_bar)
65359f464c52Smaya    {
65369f464c52Smaya        ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
65379f464c52Smaya        IM_ASSERT(tab_bar->WantLayout);         // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem()
65389f464c52Smaya        ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
65399f464c52Smaya        TabBarRemoveTab(tab_bar, tab_id);
65409f464c52Smaya    }
65419f464c52Smaya}
65429f464c52Smaya
65439f464c52SmayaImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
65449f464c52Smaya{
65459f464c52Smaya    ImGuiContext& g = *GImGui;
65469f464c52Smaya    ImVec2 label_size = CalcTextSize(label, NULL, true);
65479f464c52Smaya    ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
65489f464c52Smaya    if (has_close_button)
65499f464c52Smaya        size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
65509f464c52Smaya    else
65519f464c52Smaya        size.x += g.Style.FramePadding.x + 1.0f;
65529f464c52Smaya    return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
65539f464c52Smaya}
65549f464c52Smaya
65559f464c52Smayavoid ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
65569f464c52Smaya{
65579f464c52Smaya    // 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.
65589f464c52Smaya    ImGuiContext& g = *GImGui;
65599f464c52Smaya    const float width = bb.GetWidth();
65609f464c52Smaya    IM_UNUSED(flags);
65619f464c52Smaya    IM_ASSERT(width > 0.0f);
65629f464c52Smaya    const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f));
65639f464c52Smaya    const float y1 = bb.Min.y + 1.0f;
65649f464c52Smaya    const float y2 = bb.Max.y - 1.0f;
65659f464c52Smaya    draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
65669f464c52Smaya    draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
65679f464c52Smaya    draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
65689f464c52Smaya    draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
65699f464c52Smaya    draw_list->PathFillConvex(col);
65709f464c52Smaya    if (g.Style.TabBorderSize > 0.0f)
65719f464c52Smaya    {
65729f464c52Smaya        draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
65739f464c52Smaya        draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
65749f464c52Smaya        draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
65759f464c52Smaya        draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
65769f464c52Smaya        draw_list->PathStroke(GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize);
65779f464c52Smaya    }
65789f464c52Smaya}
65799f464c52Smaya
65809f464c52Smaya// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
65819f464c52Smaya// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
65829f464c52Smayabool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id)
65839f464c52Smaya{
65849f464c52Smaya    ImGuiContext& g = *GImGui;
65859f464c52Smaya    ImVec2 label_size = CalcTextSize(label, NULL, true);
65869f464c52Smaya    if (bb.GetWidth() <= 1.0f)
65879f464c52Smaya        return false;
65889f464c52Smaya
65899f464c52Smaya    // Render text label (with clipping + alpha gradient) + unsaved marker
65909f464c52Smaya    const char* TAB_UNSAVED_MARKER = "*";
65919f464c52Smaya    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);
65929f464c52Smaya    if (flags & ImGuiTabItemFlags_UnsavedDocument)
65939f464c52Smaya    {
65949f464c52Smaya        text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;
65959f464c52Smaya        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 + (float)(int)(-g.FontSize * 0.25f));
65969f464c52Smaya        RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL);
65979f464c52Smaya    }
65989f464c52Smaya    ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
65999f464c52Smaya
66009f464c52Smaya    // Close Button
66019f464c52Smaya    // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
66029f464c52Smaya    //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
66039f464c52Smaya    //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
66049f464c52Smaya    //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
66059f464c52Smaya    bool close_button_pressed = false;
66069f464c52Smaya    bool close_button_visible = false;
66079f464c52Smaya    if (close_button_id != 0)
66089f464c52Smaya        if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == close_button_id)
66099f464c52Smaya            close_button_visible = true;
66109f464c52Smaya    if (close_button_visible)
66119f464c52Smaya    {
66129f464c52Smaya        ImGuiItemHoveredDataBackup last_item_backup;
66139f464c52Smaya        const float close_button_sz = g.FontSize * 0.5f;
66149f464c52Smaya        if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x - close_button_sz, bb.Min.y + frame_padding.y + close_button_sz), close_button_sz))
66159f464c52Smaya            close_button_pressed = true;
66169f464c52Smaya        last_item_backup.Restore();
66179f464c52Smaya
66189f464c52Smaya        // Close with middle mouse button
66199f464c52Smaya        if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
66209f464c52Smaya            close_button_pressed = true;
66219f464c52Smaya
66229f464c52Smaya        text_pixel_clip_bb.Max.x -= close_button_sz * 2.0f;
66239f464c52Smaya    }
66249f464c52Smaya
66259f464c52Smaya    // Label with ellipsis
66269f464c52Smaya    // FIXME: This should be extracted into a helper but the use of text_pixel_clip_bb and !close_button_visible makes it tricky to abstract at the moment
66279f464c52Smaya    const char* label_display_end = FindRenderedTextEnd(label);
66289f464c52Smaya    if (label_size.x > text_ellipsis_clip_bb.GetWidth())
66299f464c52Smaya    {
66309f464c52Smaya        const int ellipsis_dot_count = 3;
66319f464c52Smaya        const float ellipsis_width = (1.0f + 1.0f) * ellipsis_dot_count - 1.0f;
66329f464c52Smaya        const char* label_end = NULL;
66339f464c52Smaya        float label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, text_ellipsis_clip_bb.GetWidth() - ellipsis_width + 1.0f, 0.0f, label, label_display_end, &label_end).x;
66349f464c52Smaya        if (label_end == label && label_end < label_display_end)    // Always display at least 1 character if there's no room for character + ellipsis
66359f464c52Smaya        {
66369f464c52Smaya            label_end = label + ImTextCountUtf8BytesFromChar(label, label_display_end);
66379f464c52Smaya            label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label, label_end).x;
66389f464c52Smaya        }
66399f464c52Smaya        while (label_end > label && ImCharIsBlankA(label_end[-1])) // Trim trailing space
66409f464c52Smaya        {
66419f464c52Smaya            label_end--;
66429f464c52Smaya            label_size_clipped_x -= g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label_end, label_end + 1).x; // Ascii blanks are always 1 byte
66439f464c52Smaya        }
66449f464c52Smaya        RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_end, &label_size, ImVec2(0.0f, 0.0f));
66459f464c52Smaya
66469f464c52Smaya        const float ellipsis_x = text_pixel_clip_bb.Min.x + label_size_clipped_x + 1.0f;
66479f464c52Smaya        if (!close_button_visible && ellipsis_x + ellipsis_width <= bb.Max.x)
66489f464c52Smaya            RenderPixelEllipsis(draw_list, ImVec2(ellipsis_x, text_pixel_clip_bb.Min.y), ellipsis_dot_count, GetColorU32(ImGuiCol_Text));
66499f464c52Smaya    }
66509f464c52Smaya    else
66519f464c52Smaya    {
66529f464c52Smaya        RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_display_end, &label_size, ImVec2(0.0f, 0.0f));
66539f464c52Smaya    }
66549f464c52Smaya
66559f464c52Smaya    return close_button_pressed;
66569f464c52Smaya}
6657