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