Path: blob/21.2-virgl/src/imgui/imgui_widgets.cpp
4558 views
// dear imgui, v1.68 WIP1// (widgets code)23/*45Index of this file:67// [SECTION] Forward Declarations8// [SECTION] Widgets: Text, etc.9// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)10// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)11// [SECTION] Widgets: ComboBox12// [SECTION] Data Type and Data Formatting Helpers13// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.14// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.15// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.16// [SECTION] Widgets: InputText, InputTextMultiline17// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.18// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.19// [SECTION] Widgets: Selectable20// [SECTION] Widgets: ListBox21// [SECTION] Widgets: PlotLines, PlotHistogram22// [SECTION] Widgets: Value helpers23// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.24// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.25// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.2627*/2829#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)30#define _CRT_SECURE_NO_WARNINGS31#endif3233#include "imgui.h"34#ifndef IMGUI_DEFINE_MATH_OPERATORS35#define IMGUI_DEFINE_MATH_OPERATORS36#endif37#include "imgui_internal.h"3839#include <ctype.h> // toupper, isprint40#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier41#include <stddef.h> // intptr_t42#else43#include <stdint.h> // intptr_t44#endif4546// Visual Studio warnings47#ifdef _MSC_VER48#pragma warning (disable: 4127) // condition expression is constant49#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen50#endif5152// Clang/GCC warnings with -Weverything53#ifdef __clang__54#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse.55#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.56#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.57#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //58#if __has_warning("-Wzero-as-null-pointer-constant")59#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning : zero as null pointer constant // some standard header variations use #define NULL 060#endif61#if __has_warning("-Wdouble-promotion")62#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.63#endif64#elif defined(__GNUC__)65#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked66#if __GNUC__ >= 867#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 instead68#endif69#endif7071//-------------------------------------------------------------------------72// Data73//-------------------------------------------------------------------------7475// Those MIN/MAX values are not define because we need to point to them76static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);77static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)78static const ImU32 IM_U32_MIN = 0;79static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)80#ifdef LLONG_MIN81static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);82static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);83#else84static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;85static const ImS64 IM_S64_MAX = 9223372036854775807LL;86#endif87static const ImU64 IM_U64_MIN = 0;88#ifdef ULLONG_MAX89static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);90#else91static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);92#endif9394//-------------------------------------------------------------------------95// [SECTION] Forward Declarations96//-------------------------------------------------------------------------9798// Data Type helpers99static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);100static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);101static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);102103// For InputTextEx()104static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);105static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);106static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);107108//-------------------------------------------------------------------------109// [SECTION] Widgets: Text, etc.110//-------------------------------------------------------------------------111// - TextUnformatted()112// - Text()113// - TextV()114// - TextColored()115// - TextColoredV()116// - TextDisabled()117// - TextDisabledV()118// - TextWrapped()119// - TextWrappedV()120// - LabelText()121// - LabelTextV()122// - BulletText()123// - BulletTextV()124//-------------------------------------------------------------------------125126void ImGui::TextUnformatted(const char* text, const char* text_end)127{128ImGuiWindow* window = GetCurrentWindow();129if (window->SkipItems)130return;131132ImGuiContext& g = *GImGui;133IM_ASSERT(text != NULL);134const char* text_begin = text;135if (text_end == NULL)136text_end = text + strlen(text); // FIXME-OPT137138const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);139const float wrap_pos_x = window->DC.TextWrapPos;140const bool wrap_enabled = wrap_pos_x >= 0.0f;141if (text_end - text > 2000 && !wrap_enabled)142{143// Long text!144// Perform manual coarse clipping to optimize for long multi-line text145// - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.146// - 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.147// - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.148const char* line = text;149const float line_height = GetTextLineHeight();150const ImRect clip_rect = window->ClipRect;151ImVec2 text_size(0,0);152153if (text_pos.y <= clip_rect.Max.y)154{155ImVec2 pos = text_pos;156157// Lines to skip (can't skip when logging text)158if (!g.LogEnabled)159{160int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);161if (lines_skippable > 0)162{163int lines_skipped = 0;164while (line < text_end && lines_skipped < lines_skippable)165{166const char* line_end = (const char*)memchr(line, '\n', text_end - line);167if (!line_end)168line_end = text_end;169line = line_end + 1;170lines_skipped++;171}172pos.y += lines_skipped * line_height;173}174}175176// Lines to render177if (line < text_end)178{179ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));180while (line < text_end)181{182if (IsClippedEx(line_rect, 0, false))183break;184185const char* line_end = (const char*)memchr(line, '\n', text_end - line);186if (!line_end)187line_end = text_end;188const ImVec2 line_size = CalcTextSize(line, line_end, false);189text_size.x = ImMax(text_size.x, line_size.x);190RenderText(pos, line, line_end, false);191line = line_end + 1;192line_rect.Min.y += line_height;193line_rect.Max.y += line_height;194pos.y += line_height;195}196197// Count remaining lines198int lines_skipped = 0;199while (line < text_end)200{201const char* line_end = (const char*)memchr(line, '\n', text_end - line);202if (!line_end)203line_end = text_end;204line = line_end + 1;205lines_skipped++;206}207pos.y += lines_skipped * line_height;208}209210text_size.y += (pos - text_pos).y;211}212213ImRect bb(text_pos, text_pos + text_size);214ItemSize(text_size);215ItemAdd(bb, 0);216}217else218{219const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;220const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);221222// Account of baseline offset223ImRect bb(text_pos, text_pos + text_size);224ItemSize(text_size);225if (!ItemAdd(bb, 0))226return;227228// Render (we don't hide text after ## in this end-user function)229RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);230}231}232233void ImGui::Text(const char* fmt, ...)234{235va_list args;236va_start(args, fmt);237TextV(fmt, args);238va_end(args);239}240241void ImGui::TextV(const char* fmt, va_list args)242{243ImGuiWindow* window = GetCurrentWindow();244if (window->SkipItems)245return;246247ImGuiContext& g = *GImGui;248const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);249TextUnformatted(g.TempBuffer, text_end);250}251252void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)253{254va_list args;255va_start(args, fmt);256TextColoredV(col, fmt, args);257va_end(args);258}259260void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)261{262PushStyleColor(ImGuiCol_Text, col);263TextV(fmt, args);264PopStyleColor();265}266267void ImGui::TextDisabled(const char* fmt, ...)268{269va_list args;270va_start(args, fmt);271TextDisabledV(fmt, args);272va_end(args);273}274275void ImGui::TextDisabledV(const char* fmt, va_list args)276{277PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);278TextV(fmt, args);279PopStyleColor();280}281282void ImGui::TextWrapped(const char* fmt, ...)283{284va_list args;285va_start(args, fmt);286TextWrappedV(fmt, args);287va_end(args);288}289290void ImGui::TextWrappedV(const char* fmt, va_list args)291{292bool need_backup = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set293if (need_backup)294PushTextWrapPos(0.0f);295TextV(fmt, args);296if (need_backup)297PopTextWrapPos();298}299300void ImGui::LabelText(const char* label, const char* fmt, ...)301{302va_list args;303va_start(args, fmt);304LabelTextV(label, fmt, args);305va_end(args);306}307308// Add a label+text combo aligned to other label+value widgets309void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)310{311ImGuiWindow* window = GetCurrentWindow();312if (window->SkipItems)313return;314315ImGuiContext& g = *GImGui;316const ImGuiStyle& style = g.Style;317const float w = CalcItemWidth();318319const ImVec2 label_size = CalcTextSize(label, NULL, true);320const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));321const 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);322ItemSize(total_bb, style.FramePadding.y);323if (!ItemAdd(total_bb, 0))324return;325326// Render327const char* value_text_begin = &g.TempBuffer[0];328const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);329RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));330if (label_size.x > 0.0f)331RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);332}333334void ImGui::BulletText(const char* fmt, ...)335{336va_list args;337va_start(args, fmt);338BulletTextV(fmt, args);339va_end(args);340}341342// Text with a little bullet aligned to the typical tree node.343void ImGui::BulletTextV(const char* fmt, va_list args)344{345ImGuiWindow* window = GetCurrentWindow();346if (window->SkipItems)347return;348349ImGuiContext& g = *GImGui;350const ImGuiStyle& style = g.Style;351352const char* text_begin = g.TempBuffer;353const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);354const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);355const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it356const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);357const 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 padding358ItemSize(bb);359if (!ItemAdd(bb, 0))360return;361362// Render363RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));364RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);365}366367//-------------------------------------------------------------------------368// [SECTION] Widgets: Main369//-------------------------------------------------------------------------370// - ButtonBehavior() [Internal]371// - Button()372// - SmallButton()373// - InvisibleButton()374// - ArrowButton()375// - CloseButton() [Internal]376// - CollapseButton() [Internal]377// - Scrollbar() [Internal]378// - Image()379// - ImageButton()380// - Checkbox()381// - CheckboxFlags()382// - RadioButton()383// - ProgressBar()384// - Bullet()385//-------------------------------------------------------------------------386387bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)388{389ImGuiContext& g = *GImGui;390ImGuiWindow* window = GetCurrentWindow();391392if (flags & ImGuiButtonFlags_Disabled)393{394if (out_hovered) *out_hovered = false;395if (out_held) *out_held = false;396if (g.ActiveId == id) ClearActiveID();397return false;398}399400// Default behavior requires click+release on same spot401if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)402flags |= ImGuiButtonFlags_PressedOnClickRelease;403404ImGuiWindow* backup_hovered_window = g.HoveredWindow;405if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)406g.HoveredWindow = window;407408#ifdef IMGUI_ENABLE_TEST_ENGINE409if (id != 0 && window->DC.LastItemId != id)410ImGuiTestEngineHook_ItemAdd(&g, bb, id);411#endif412413bool pressed = false;414bool hovered = ItemHoverable(bb, id);415416// Drag source doesn't report as hovered417if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))418hovered = false;419420// Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button421if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))422if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))423{424hovered = true;425SetHoveredID(id);426if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy427{428pressed = true;429FocusWindow(window);430}431}432433if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)434g.HoveredWindow = backup_hovered_window;435436// 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.437if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))438hovered = false;439440// Mouse441if (hovered)442{443if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))444{445// | CLICKING | HOLDING with ImGuiButtonFlags_Repeat446// PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds447// PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..448// PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)449// PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..450// FIXME-NAV: We don't honor those different behaviors.451if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])452{453SetActiveID(id, window);454if (!(flags & ImGuiButtonFlags_NoNavFocus))455SetFocusID(id, window);456FocusWindow(window);457}458if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))459{460pressed = true;461if (flags & ImGuiButtonFlags_NoHoldingActiveID)462ClearActiveID();463else464SetActiveID(id, window); // Hold on ID465FocusWindow(window);466}467if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])468{469if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>470pressed = true;471ClearActiveID();472}473474// 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).475// Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.476if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))477pressed = true;478}479480if (pressed)481g.NavDisableHighlight = true;482}483484// Gamepad/Keyboard navigation485// We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.486if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))487hovered = true;488489if (g.NavActivateDownId == id)490{491bool nav_activated_by_code = (g.NavActivateId == id);492bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);493if (nav_activated_by_code || nav_activated_by_inputs)494pressed = true;495if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)496{497// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.498g.NavActivateId = id; // This is so SetActiveId assign a Nav source499SetActiveID(id, window);500if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus))501SetFocusID(id, window);502g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);503}504}505506bool held = false;507if (g.ActiveId == id)508{509if (pressed)510g.ActiveIdHasBeenPressed = true;511if (g.ActiveIdSource == ImGuiInputSource_Mouse)512{513if (g.ActiveIdIsJustActivated)514g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;515if (g.IO.MouseDown[0])516{517held = true;518}519else520{521if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))522if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>523if (!g.DragDropActive)524pressed = true;525ClearActiveID();526}527if (!(flags & ImGuiButtonFlags_NoNavFocus))528g.NavDisableHighlight = true;529}530else if (g.ActiveIdSource == ImGuiInputSource_Nav)531{532if (g.NavActivateDownId != id)533ClearActiveID();534}535}536537if (out_hovered) *out_hovered = hovered;538if (out_held) *out_held = held;539540return pressed;541}542543bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)544{545ImGuiWindow* window = GetCurrentWindow();546if (window->SkipItems)547return false;548549ImGuiContext& g = *GImGui;550const ImGuiStyle& style = g.Style;551const ImGuiID id = window->GetID(label);552const ImVec2 label_size = CalcTextSize(label, NULL, true);553554ImVec2 pos = window->DC.CursorPos;555if ((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)556pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;557ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);558559const ImRect bb(pos, pos + size);560ItemSize(size, style.FramePadding.y);561if (!ItemAdd(bb, id))562return false;563564if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)565flags |= ImGuiButtonFlags_Repeat;566bool hovered, held;567bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);568if (pressed)569MarkItemEdited(id);570571// Render572const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);573RenderNavHighlight(bb, id);574RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);575RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);576577// Automatically close popups578//if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))579// CloseCurrentPopup();580581IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);582return pressed;583}584585bool ImGui::Button(const char* label, const ImVec2& size_arg)586{587return ButtonEx(label, size_arg, 0);588}589590// Small buttons fits within text without additional vertical spacing.591bool ImGui::SmallButton(const char* label)592{593ImGuiContext& g = *GImGui;594float backup_padding_y = g.Style.FramePadding.y;595g.Style.FramePadding.y = 0.0f;596bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);597g.Style.FramePadding.y = backup_padding_y;598return pressed;599}600601// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.602// 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)603bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)604{605ImGuiWindow* window = GetCurrentWindow();606if (window->SkipItems)607return false;608609// Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.610IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);611612const ImGuiID id = window->GetID(str_id);613ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);614const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);615ItemSize(size);616if (!ItemAdd(bb, id))617return false;618619bool hovered, held;620bool pressed = ButtonBehavior(bb, id, &hovered, &held);621622return pressed;623}624625bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)626{627ImGuiWindow* window = GetCurrentWindow();628if (window->SkipItems)629return false;630631ImGuiContext& g = *GImGui;632const ImGuiID id = window->GetID(str_id);633const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);634const float default_size = GetFrameHeight();635ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);636if (!ItemAdd(bb, id))637return false;638639if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)640flags |= ImGuiButtonFlags_Repeat;641642bool hovered, held;643bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);644645// Render646const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);647RenderNavHighlight(bb, id);648RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);649RenderArrow(bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), dir);650651return pressed;652}653654bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)655{656float sz = GetFrameHeight();657return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);658}659660// Button to close a window661bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)662{663ImGuiContext& g = *GImGui;664ImGuiWindow* window = g.CurrentWindow;665666// We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.667// (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).668const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));669bool is_clipped = !ItemAdd(bb, id);670671bool hovered, held;672bool pressed = ButtonBehavior(bb, id, &hovered, &held);673if (is_clipped)674return pressed;675676// Render677ImVec2 center = bb.GetCenter();678if (hovered)679window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);680681float cross_extent = (radius * 0.7071f) - 1.0f;682ImU32 cross_col = GetColorU32(ImGuiCol_Text);683center -= ImVec2(0.5f, 0.5f);684window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);685window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);686687return pressed;688}689690bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)691{692ImGuiContext& g = *GImGui;693ImGuiWindow* window = g.CurrentWindow;694695ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);696ItemAdd(bb, id);697bool hovered, held;698bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);699700ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);701if (hovered || held)702window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9);703RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);704705// Switch to moving the window after mouse is moved beyond the initial drag threshold706if (IsItemActive() && IsMouseDragging())707StartMouseMovingWindow(window);708709return pressed;710}711712ImGuiID ImGui::GetScrollbarID(ImGuiLayoutType direction)713{714ImGuiContext& g = *GImGui;715ImGuiWindow* window = g.CurrentWindow;716return window->GetID((direction == ImGuiLayoutType_Horizontal) ? "#SCROLLX" : "#SCROLLY");717}718719// Vertical/Horizontal scrollbar720// The entire piece of code below is rather confusing because:721// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)722// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar723// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.724void ImGui::Scrollbar(ImGuiLayoutType direction)725{726ImGuiContext& g = *GImGui;727ImGuiWindow* window = g.CurrentWindow;728729const bool horizontal = (direction == ImGuiLayoutType_Horizontal);730const ImGuiStyle& style = g.Style;731const ImGuiID id = GetScrollbarID(direction);732733// Render background734bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);735float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;736const ImRect window_rect = window->Rect();737const float border_size = window->WindowBorderSize;738ImRect bb = horizontal739? 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)740: 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);741if (!horizontal)742bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);743744const float bb_height = bb.GetHeight();745if (bb.GetWidth() <= 0.0f || bb_height <= 0.0f)746return;747748// 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)749float alpha = 1.0f;750if ((direction == ImGuiLayoutType_Vertical) && bb_height < g.FontSize + g.Style.FramePadding.y * 2.0f)751{752alpha = ImSaturate((bb_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));753if (alpha <= 0.0f)754return;755}756const bool allow_interaction = (alpha >= 1.0f);757758int window_rounding_corners;759if (horizontal)760window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);761else762window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);763window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);764bb.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)));765766// V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)767float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();768float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;769float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;770float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;771772// Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)773// But we maintain a minimum size in pixel to allow for the user to still aim inside.774IM_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.775const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);776const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);777const float grab_h_norm = grab_h_pixels / scrollbar_size_v;778779// Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().780bool held = false;781bool hovered = false;782const bool previously_held = (g.ActiveId == id);783ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);784785float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);786float scroll_ratio = ImSaturate(scroll_v / scroll_max);787float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;788if (held && allow_interaction && grab_h_norm < 1.0f)789{790float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;791float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;792float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;793794// Click position in scrollbar normalized space (0.0f->1.0f)795const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);796SetHoveredID(id);797798bool seek_absolute = false;799if (!previously_held)800{801// On initial click calculate the distance between mouse and the center of the grab802if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)803{804*click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;805}806else807{808seek_absolute = true;809*click_delta_to_grab_center_v = 0.0f;810}811}812813// Apply scroll814// 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 position815const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm));816scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));817if (horizontal)818window->Scroll.x = scroll_v;819else820window->Scroll.y = scroll_v;821822// Update values for rendering823scroll_ratio = ImSaturate(scroll_v / scroll_max);824grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;825826// Update distance to grab now that we have seeked and saturated827if (seek_absolute)828*click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;829}830831// Render grab832const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);833ImRect grab_rect;834if (horizontal)835grab_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);836else837grab_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));838window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);839}840841void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)842{843ImGuiWindow* window = GetCurrentWindow();844if (window->SkipItems)845return;846847ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);848if (border_col.w > 0.0f)849bb.Max += ImVec2(2, 2);850ItemSize(bb);851if (!ItemAdd(bb, 0))852return;853854if (border_col.w > 0.0f)855{856window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);857window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));858}859else860{861window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));862}863}864865// frame_padding < 0: uses FramePadding from style (default)866// frame_padding = 0: no framing867// frame_padding > 0: set framing size868// The color used are the button colors.869bool 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)870{871ImGuiWindow* window = GetCurrentWindow();872if (window->SkipItems)873return false;874875ImGuiContext& g = *GImGui;876const ImGuiStyle& style = g.Style;877878// Default to using texture ID as ID. User can still push string/integer prefixes.879// We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.880PushID((void*)(intptr_t)user_texture_id);881const ImGuiID id = window->GetID("#image");882PopID();883884const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;885const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);886const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);887ItemSize(bb);888if (!ItemAdd(bb, id))889return false;890891bool hovered, held;892bool pressed = ButtonBehavior(bb, id, &hovered, &held);893894// Render895const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);896RenderNavHighlight(bb, id);897RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));898if (bg_col.w > 0.0f)899window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));900window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));901902return pressed;903}904905bool ImGui::Checkbox(const char* label, bool* v)906{907ImGuiWindow* window = GetCurrentWindow();908if (window->SkipItems)909return false;910911ImGuiContext& g = *GImGui;912const ImGuiStyle& style = g.Style;913const ImGuiID id = window->GetID(label);914const ImVec2 label_size = CalcTextSize(label, NULL, true);915916const float square_sz = GetFrameHeight();917const ImVec2 pos = window->DC.CursorPos;918const 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));919ItemSize(total_bb, style.FramePadding.y);920if (!ItemAdd(total_bb, id))921return false;922923bool hovered, held;924bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);925if (pressed)926{927*v = !(*v);928MarkItemEdited(id);929}930931const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));932RenderNavHighlight(total_bb, id);933RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);934if (*v)935{936const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));937RenderCheckMark(check_bb.Min + ImVec2(pad, pad), GetColorU32(ImGuiCol_CheckMark), square_sz - pad*2.0f);938}939940if (g.LogEnabled)941LogRenderedText(&total_bb.Min, *v ? "[x]" : "[ ]");942if (label_size.x > 0.0f)943RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);944945IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));946return pressed;947}948949bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)950{951bool v = ((*flags & flags_value) == flags_value);952bool pressed = Checkbox(label, &v);953if (pressed)954{955if (v)956*flags |= flags_value;957else958*flags &= ~flags_value;959}960961return pressed;962}963964bool ImGui::RadioButton(const char* label, bool active)965{966ImGuiWindow* window = GetCurrentWindow();967if (window->SkipItems)968return false;969970ImGuiContext& g = *GImGui;971const ImGuiStyle& style = g.Style;972const ImGuiID id = window->GetID(label);973const ImVec2 label_size = CalcTextSize(label, NULL, true);974975const float square_sz = GetFrameHeight();976const ImVec2 pos = window->DC.CursorPos;977const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));978const 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));979ItemSize(total_bb, style.FramePadding.y);980if (!ItemAdd(total_bb, id))981return false;982983ImVec2 center = check_bb.GetCenter();984center.x = (float)(int)center.x + 0.5f;985center.y = (float)(int)center.y + 0.5f;986const float radius = (square_sz - 1.0f) * 0.5f;987988bool hovered, held;989bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);990if (pressed)991MarkItemEdited(id);992993RenderNavHighlight(total_bb, id);994window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);995if (active)996{997const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));998window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);999}10001001if (style.FrameBorderSize > 0.0f)1002{1003window->DrawList->AddCircle(center + ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);1004window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);1005}10061007if (g.LogEnabled)1008LogRenderedText(&total_bb.Min, active ? "(x)" : "( )");1009if (label_size.x > 0.0f)1010RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);10111012return pressed;1013}10141015bool ImGui::RadioButton(const char* label, int* v, int v_button)1016{1017const bool pressed = RadioButton(label, *v == v_button);1018if (pressed)1019*v = v_button;1020return pressed;1021}10221023// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size1024void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)1025{1026ImGuiWindow* window = GetCurrentWindow();1027if (window->SkipItems)1028return;10291030ImGuiContext& g = *GImGui;1031const ImGuiStyle& style = g.Style;10321033ImVec2 pos = window->DC.CursorPos;1034ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));1035ItemSize(bb, style.FramePadding.y);1036if (!ItemAdd(bb, 0))1037return;10381039// Render1040fraction = ImSaturate(fraction);1041RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);1042bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));1043const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);1044RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);10451046// Default displaying the fraction as percentage string, but user can override it1047char overlay_buf[32];1048if (!overlay)1049{1050ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);1051overlay = overlay_buf;1052}10531054ImVec2 overlay_size = CalcTextSize(overlay, NULL);1055if (overlay_size.x > 0.0f)1056RenderTextClipped(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);1057}10581059void ImGui::Bullet()1060{1061ImGuiWindow* window = GetCurrentWindow();1062if (window->SkipItems)1063return;10641065ImGuiContext& g = *GImGui;1066const ImGuiStyle& style = g.Style;1067const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);1068const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));1069ItemSize(bb);1070if (!ItemAdd(bb, 0))1071{1072SameLine(0, style.FramePadding.x*2);1073return;1074}10751076// Render and stay on same line1077RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));1078SameLine(0, style.FramePadding.x*2);1079}10801081//-------------------------------------------------------------------------1082// [SECTION] Widgets: Low-level Layout helpers1083//-------------------------------------------------------------------------1084// - Spacing()1085// - Dummy()1086// - NewLine()1087// - AlignTextToFramePadding()1088// - Separator()1089// - VerticalSeparator() [Internal]1090// - SplitterBehavior() [Internal]1091//-------------------------------------------------------------------------10921093void ImGui::Spacing()1094{1095ImGuiWindow* window = GetCurrentWindow();1096if (window->SkipItems)1097return;1098ItemSize(ImVec2(0,0));1099}11001101void ImGui::Dummy(const ImVec2& size)1102{1103ImGuiWindow* window = GetCurrentWindow();1104if (window->SkipItems)1105return;11061107const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);1108ItemSize(bb);1109ItemAdd(bb, 0);1110}11111112void ImGui::NewLine()1113{1114ImGuiWindow* window = GetCurrentWindow();1115if (window->SkipItems)1116return;11171118ImGuiContext& g = *GImGui;1119const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;1120window->DC.LayoutType = ImGuiLayoutType_Vertical;1121if (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.1122ItemSize(ImVec2(0,0));1123else1124ItemSize(ImVec2(0.0f, g.FontSize));1125window->DC.LayoutType = backup_layout_type;1126}11271128void ImGui::AlignTextToFramePadding()1129{1130ImGuiWindow* window = GetCurrentWindow();1131if (window->SkipItems)1132return;11331134ImGuiContext& g = *GImGui;1135window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);1136window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);1137}11381139// Horizontal/vertical separating line1140void ImGui::Separator()1141{1142ImGuiWindow* window = GetCurrentWindow();1143if (window->SkipItems)1144return;1145ImGuiContext& g = *GImGui;11461147// Those flags should eventually be overridable by the user1148ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;1149IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected1150if (flags & ImGuiSeparatorFlags_Vertical)1151{1152VerticalSeparator();1153return;1154}11551156// Horizontal Separator1157if (window->DC.ColumnsSet)1158PopClipRect();11591160float x1 = window->Pos.x;1161float x2 = window->Pos.x + window->Size.x;1162if (!window->DC.GroupStack.empty())1163x1 += window->DC.Indent.x;11641165const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));1166ItemSize(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.1167if (!ItemAdd(bb, 0))1168{1169if (window->DC.ColumnsSet)1170PushColumnClipRect();1171return;1172}11731174window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));11751176if (g.LogEnabled)1177LogRenderedText(&bb.Min, "--------------------------------");11781179if (window->DC.ColumnsSet)1180{1181PushColumnClipRect();1182window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;1183}1184}11851186void ImGui::VerticalSeparator()1187{1188ImGuiWindow* window = GetCurrentWindow();1189if (window->SkipItems)1190return;1191ImGuiContext& g = *GImGui;11921193float y1 = window->DC.CursorPos.y;1194float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y;1195const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));1196ItemSize(ImVec2(bb.GetWidth(), 0.0f));1197if (!ItemAdd(bb, 0))1198return;11991200window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));1201if (g.LogEnabled)1202LogText(" |");1203}12041205// 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.1206bool 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)1207{1208ImGuiContext& g = *GImGui;1209ImGuiWindow* window = g.CurrentWindow;12101211const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;1212window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;1213bool item_add = ItemAdd(bb, id);1214window->DC.ItemFlags = item_flags_backup;1215if (!item_add)1216return false;12171218bool hovered, held;1219ImRect bb_interact = bb;1220bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));1221ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);1222if (g.ActiveId != id)1223SetItemAllowOverlap();12241225if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))1226SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);12271228ImRect bb_render = bb;1229if (held)1230{1231ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;1232float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;12331234// Minimum pane size1235float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);1236float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);1237if (mouse_delta < -size_1_maximum_delta)1238mouse_delta = -size_1_maximum_delta;1239if (mouse_delta > size_2_maximum_delta)1240mouse_delta = size_2_maximum_delta;12411242// Apply resize1243if (mouse_delta != 0.0f)1244{1245if (mouse_delta < 0.0f)1246IM_ASSERT(*size1 + mouse_delta >= min_size1);1247if (mouse_delta > 0.0f)1248IM_ASSERT(*size2 - mouse_delta >= min_size2);1249*size1 += mouse_delta;1250*size2 -= mouse_delta;1251bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));1252MarkItemEdited(id);1253}1254}12551256// Render1257const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);1258window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);12591260return held;1261}12621263//-------------------------------------------------------------------------1264// [SECTION] Widgets: ComboBox1265//-------------------------------------------------------------------------1266// - BeginCombo()1267// - EndCombo()1268// - Combo()1269//-------------------------------------------------------------------------12701271static float CalcMaxPopupHeightFromItemCount(int items_count)1272{1273ImGuiContext& g = *GImGui;1274if (items_count <= 0)1275return FLT_MAX;1276return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);1277}12781279bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)1280{1281// Always consume the SetNextWindowSizeConstraint() call in our early return paths1282ImGuiContext& g = *GImGui;1283ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;1284g.NextWindowData.SizeConstraintCond = 0;12851286ImGuiWindow* window = GetCurrentWindow();1287if (window->SkipItems)1288return false;12891290IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together12911292const ImGuiStyle& style = g.Style;1293const ImGuiID id = window->GetID(label);12941295const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();1296const ImVec2 label_size = CalcTextSize(label, NULL, true);1297const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();1298const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));1299const 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));1300ItemSize(total_bb, style.FramePadding.y);1301if (!ItemAdd(total_bb, id, &frame_bb))1302return false;13031304bool hovered, held;1305bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);1306bool popup_open = IsPopupOpen(id);13071308const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));1309const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);1310RenderNavHighlight(frame_bb, id);1311if (!(flags & ImGuiComboFlags_NoPreview))1312window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);1313if (!(flags & ImGuiComboFlags_NoArrowButton))1314{1315window->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);1316RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);1317}1318RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);1319if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))1320RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));1321if (label_size.x > 0)1322RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);13231324if ((pressed || g.NavActivateId == id) && !popup_open)1325{1326if (window->DC.NavLayerCurrent == 0)1327window->NavLastIds[0] = id;1328OpenPopupEx(id);1329popup_open = true;1330}13311332if (!popup_open)1333return false;13341335if (backup_next_window_size_constraint)1336{1337g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;1338g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);1339}1340else1341{1342if ((flags & ImGuiComboFlags_HeightMask_) == 0)1343flags |= ImGuiComboFlags_HeightRegular;1344IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one1345int popup_max_height_in_items = -1;1346if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;1347else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;1348else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;1349SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));1350}13511352char name[16];1353ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth13541355// Peak into expected window size so we can position it1356if (ImGuiWindow* popup_window = FindWindowByName(name))1357if (popup_window->WasActive)1358{1359ImVec2 size_expected = CalcWindowExpectedSize(popup_window);1360if (flags & ImGuiComboFlags_PopupAlignLeft)1361popup_window->AutoPosLastDirection = ImGuiDir_Left;1362ImRect r_outer = GetWindowAllowedExtentRect(popup_window);1363ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);1364SetNextWindowPos(pos);1365}13661367// Horizontally align ourselves with the framed text1368ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;1369PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));1370bool ret = Begin(name, NULL, window_flags);1371PopStyleVar();1372if (!ret)1373{1374EndPopup();1375IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above1376return false;1377}1378return true;1379}13801381void ImGui::EndCombo()1382{1383EndPopup();1384}13851386// Getter for the old Combo() API: const char*[]1387static bool Items_ArrayGetter(void* data, int idx, const char** out_text)1388{1389const char* const* items = (const char* const*)data;1390if (out_text)1391*out_text = items[idx];1392return true;1393}13941395// Getter for the old Combo() API: "item1\0item2\0item3\0"1396static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)1397{1398// FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.1399const char* items_separated_by_zeros = (const char*)data;1400int items_count = 0;1401const char* p = items_separated_by_zeros;1402while (*p)1403{1404if (idx == items_count)1405break;1406p += strlen(p) + 1;1407items_count++;1408}1409if (!*p)1410return false;1411if (out_text)1412*out_text = p;1413return true;1414}14151416// Old API, prefer using BeginCombo() nowadays if you can.1417bool 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)1418{1419ImGuiContext& g = *GImGui;14201421// Call the getter to obtain the preview string which is a parameter to BeginCombo()1422const char* preview_value = NULL;1423if (*current_item >= 0 && *current_item < items_count)1424items_getter(data, *current_item, &preview_value);14251426// 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.1427if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)1428SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));14291430if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))1431return false;14321433// Display items1434// FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)1435bool value_changed = false;1436for (int i = 0; i < items_count; i++)1437{1438PushID((void*)(intptr_t)i);1439const bool item_selected = (i == *current_item);1440const char* item_text;1441if (!items_getter(data, i, &item_text))1442item_text = "*Unknown item*";1443if (Selectable(item_text, item_selected))1444{1445value_changed = true;1446*current_item = i;1447}1448if (item_selected)1449SetItemDefaultFocus();1450PopID();1451}14521453EndCombo();1454return value_changed;1455}14561457// Combo box helper allowing to pass an array of strings.1458bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)1459{1460const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);1461return value_changed;1462}14631464// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"1465bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)1466{1467int items_count = 0;1468const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open1469while (*p)1470{1471p += strlen(p) + 1;1472items_count++;1473}1474bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);1475return value_changed;1476}14771478//-------------------------------------------------------------------------1479// [SECTION] Data Type and Data Formatting Helpers [Internal]1480//-------------------------------------------------------------------------1481// - PatchFormatStringFloatToInt()1482// - DataTypeFormatString()1483// - DataTypeApplyOp()1484// - DataTypeApplyOpFromText()1485// - GetMinimumStepAtDecimalPrecision1486// - RoundScalarWithFormat<>()1487//-------------------------------------------------------------------------14881489struct ImGuiDataTypeInfo1490{1491size_t Size;1492const char* PrintFmt; // Unused1493const char* ScanFmt;1494};14951496static const ImGuiDataTypeInfo GDataTypeInfo[] =1497{1498{ sizeof(int), "%d", "%d" },1499{ sizeof(unsigned int), "%u", "%u" },1500#ifdef _MSC_VER1501{ sizeof(ImS64), "%I64d","%I64d" },1502{ sizeof(ImU64), "%I64u","%I64u" },1503#else1504{ sizeof(ImS64), "%lld", "%lld" },1505{ sizeof(ImU64), "%llu", "%llu" },1506#endif1507{ sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg1508{ sizeof(double), "%f", "%lf" },1509};1510IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);15111512// 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".1513// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.1514// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!1515static const char* PatchFormatStringFloatToInt(const char* fmt)1516{1517if (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.1518return "%d";1519const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%)1520const 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).1521if (fmt_end > fmt_start && fmt_end[-1] == 'f')1522{1523#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS1524if (fmt_start == fmt && fmt_end[0] == 0)1525return "%d";1526ImGuiContext& g = *GImGui;1527ImFormatString(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.1528return g.TempBuffer;1529#else1530IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"1531#endif1532}1533return fmt;1534}15351536static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)1537{1538if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument1539return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr);1540if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument1541return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr);1542if (data_type == ImGuiDataType_Float)1543return ImFormatString(buf, buf_size, format, *(const float*)data_ptr);1544if (data_type == ImGuiDataType_Double)1545return ImFormatString(buf, buf_size, format, *(const double*)data_ptr);1546IM_ASSERT(0);1547return 0;1548}15491550// FIXME: Adding support for clamping on boundaries of the data type would be nice.1551static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)1552{1553IM_ASSERT(op == '+' || op == '-');1554switch (data_type)1555{1556case ImGuiDataType_S32:1557if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2;1558else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;1559return;1560case ImGuiDataType_U32:1561if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;1562else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;1563return;1564case ImGuiDataType_S64:1565if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;1566else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;1567return;1568case ImGuiDataType_U64:1569if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;1570else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;1571return;1572case ImGuiDataType_Float:1573if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2;1574else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;1575return;1576case ImGuiDataType_Double:1577if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2;1578else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;1579return;1580case ImGuiDataType_COUNT: break;1581}1582IM_ASSERT(0);1583}15841585// User can input math operators (e.g. +100) to edit a numerical values.1586// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..1587static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)1588{1589while (ImCharIsBlankA(*buf))1590buf++;15911592// We don't support '-' op because it would conflict with inputing negative value.1593// Instead you can use +-100 to subtract from an existing value1594char op = buf[0];1595if (op == '+' || op == '*' || op == '/')1596{1597buf++;1598while (ImCharIsBlankA(*buf))1599buf++;1600}1601else1602{1603op = 0;1604}1605if (!buf[0])1606return false;16071608// Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.1609IM_ASSERT(data_type < ImGuiDataType_COUNT);1610int data_backup[2];1611IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));1612memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);16131614if (format == NULL)1615format = GDataTypeInfo[data_type].ScanFmt;16161617int arg1i = 0;1618if (data_type == ImGuiDataType_S32)1619{1620int* v = (int*)data_ptr;1621int arg0i = *v;1622float arg1f = 0.0f;1623if (op && sscanf(initial_value_buf, format, &arg0i) < 1)1624return false;1625// 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 precision1626if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract)1627else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply1628else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide1629else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant1630}1631else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)1632{1633// Assign constant1634// 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.1635sscanf(buf, format, data_ptr);1636}1637else if (data_type == ImGuiDataType_Float)1638{1639// For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in1640format = "%f";1641float* v = (float*)data_ptr;1642float arg0f = *v, arg1f = 0.0f;1643if (op && sscanf(initial_value_buf, format, &arg0f) < 1)1644return false;1645if (sscanf(buf, format, &arg1f) < 1)1646return false;1647if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)1648else if (op == '*') { *v = arg0f * arg1f; } // Multiply1649else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide1650else { *v = arg1f; } // Assign constant1651}1652else if (data_type == ImGuiDataType_Double)1653{1654format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis1655double* v = (double*)data_ptr;1656double arg0f = *v, arg1f = 0.0;1657if (op && sscanf(initial_value_buf, format, &arg0f) < 1)1658return false;1659if (sscanf(buf, format, &arg1f) < 1)1660return false;1661if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)1662else if (op == '*') { *v = arg0f * arg1f; } // Multiply1663else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide1664else { *v = arg1f; } // Assign constant1665}1666return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;1667}16681669static float GetMinimumStepAtDecimalPrecision(int decimal_precision)1670{1671static 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 };1672if (decimal_precision < 0)1673return FLT_MIN;1674return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);1675}16761677template<typename TYPE>1678static const char* ImAtoi(const char* src, TYPE* output)1679{1680int negative = 0;1681if (*src == '-') { negative = 1; src++; }1682if (*src == '+') { src++; }1683TYPE v = 0;1684while (*src >= '0' && *src <= '9')1685v = (v * 10) + (*src++ - '0');1686*output = negative ? -v : v;1687return src;1688}16891690template<typename TYPE, typename SIGNEDTYPE>1691TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)1692{1693const char* fmt_start = ImParseFormatFindStart(format);1694if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string1695return v;1696char v_str[64];1697ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);1698const char* p = v_str;1699while (*p == ' ')1700p++;1701if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)1702v = (TYPE)ImAtof(p);1703else1704ImAtoi(p, (SIGNEDTYPE*)&v);1705return v;1706}17071708//-------------------------------------------------------------------------1709// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.1710//-------------------------------------------------------------------------1711// - DragBehaviorT<>() [Internal]1712// - DragBehavior() [Internal]1713// - DragScalar()1714// - DragScalarN()1715// - DragFloat()1716// - DragFloat2()1717// - DragFloat3()1718// - DragFloat4()1719// - DragFloatRange2()1720// - DragInt()1721// - DragInt2()1722// - DragInt3()1723// - DragInt4()1724// - DragIntRange2()1725//-------------------------------------------------------------------------17261727// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)1728template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>1729bool 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)1730{1731ImGuiContext& g = *GImGui;1732const ImGuiAxis axis = (flags & ImGuiDragFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;1733const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);1734const bool has_min_max = (v_min != v_max);1735const bool is_power = (power != 1.0f && is_decimal && has_min_max && (v_max - v_min < FLT_MAX));17361737// Default tweak speed1738if (v_speed == 0.0f && has_min_max && (v_max - v_min < FLT_MAX))1739v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);17401741// Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings1742float adjust_delta = 0.0f;1743if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)1744{1745adjust_delta = g.IO.MouseDelta[axis];1746if (g.IO.KeyAlt)1747adjust_delta *= 1.0f / 100.0f;1748if (g.IO.KeyShift)1749adjust_delta *= 10.0f;1750}1751else if (g.ActiveIdSource == ImGuiInputSource_Nav)1752{1753int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;1754adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];1755v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));1756}1757adjust_delta *= v_speed;17581759// For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.1760if (axis == ImGuiAxis_Y)1761adjust_delta = -adjust_delta;17621763// Clear current value on activation1764// 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.1765bool is_just_activated = g.ActiveIdIsJustActivated;1766bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));1767bool is_drag_direction_change_with_power = is_power && ((adjust_delta < 0 && g.DragCurrentAccum > 0) || (adjust_delta > 0 && g.DragCurrentAccum < 0));1768if (is_just_activated || is_already_past_limits_and_pushing_outward || is_drag_direction_change_with_power)1769{1770g.DragCurrentAccum = 0.0f;1771g.DragCurrentAccumDirty = false;1772}1773else if (adjust_delta != 0.0f)1774{1775g.DragCurrentAccum += adjust_delta;1776g.DragCurrentAccumDirty = true;1777}17781779if (!g.DragCurrentAccumDirty)1780return false;17811782TYPE v_cur = *v;1783FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;17841785if (is_power)1786{1787// 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 range1788FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);1789FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));1790v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);1791v_old_ref_for_accum_remainder = v_old_norm_curved;1792}1793else1794{1795v_cur += (TYPE)g.DragCurrentAccum;1796}17971798// Round to user desired precision based on format string1799v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);18001801// Preserve remainder after rounding has been applied. This also allow slow tweaking of values.1802g.DragCurrentAccumDirty = false;1803if (is_power)1804{1805FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);1806g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);1807}1808else1809{1810g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);1811}18121813// Lose zero sign for float/double1814if (v_cur == (TYPE)-0)1815v_cur = (TYPE)0;18161817// Clamp values (+ handle overflow/wrap-around for integer types)1818if (*v != v_cur && has_min_max)1819{1820if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal))1821v_cur = v_min;1822if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal))1823v_cur = v_max;1824}18251826// Apply result1827if (*v == v_cur)1828return false;1829*v = v_cur;1830return true;1831}18321833bool 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)1834{1835ImGuiContext& g = *GImGui;1836if (g.ActiveId == id)1837{1838if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])1839ClearActiveID();1840else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)1841ClearActiveID();1842}1843if (g.ActiveId != id)1844return false;18451846switch (data_type)1847{1848case 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);1849case 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);1850case 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);1851case 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);1852case 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);1853case 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);1854case ImGuiDataType_COUNT: break;1855}1856IM_ASSERT(0);1857return false;1858}18591860bool 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)1861{1862ImGuiWindow* window = GetCurrentWindow();1863if (window->SkipItems)1864return false;18651866if (power != 1.0f)1867IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds18681869ImGuiContext& g = *GImGui;1870const ImGuiStyle& style = g.Style;1871const ImGuiID id = window->GetID(label);1872const float w = CalcItemWidth();18731874const ImVec2 label_size = CalcTextSize(label, NULL, true);1875const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));1876const 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));18771878ItemSize(total_bb, style.FramePadding.y);1879if (!ItemAdd(total_bb, id, &frame_bb))1880return false;18811882const bool hovered = ItemHoverable(frame_bb, id);18831884// Default format string when passing NULL1885// Patch old "%.0f" format string to use "%d", read function comments for more details.1886IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);1887if (format == NULL)1888format = GDataTypeInfo[data_type].PrintFmt;1889else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)1890format = PatchFormatStringFloatToInt(format);18911892// Tabbing or CTRL-clicking on Drag turns it into an input box1893bool start_text_input = false;1894const bool tab_focus_requested = FocusableItemRegister(window, id);1895if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))1896{1897SetActiveID(id, window);1898SetFocusID(id, window);1899FocusWindow(window);1900g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);1901if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)1902{1903start_text_input = true;1904g.ScalarAsInputTextId = 0;1905}1906}1907if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))1908{1909window->DC.CursorPos = frame_bb.Min;1910FocusableItemUnregister(window);1911return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);1912}19131914// Actual drag behavior1915const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power, ImGuiDragFlags_None);1916if (value_changed)1917MarkItemEdited(id);19181919// Draw frame1920const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);1921RenderNavHighlight(frame_bb, id);1922RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);19231924// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.1925char value_buf[64];1926const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);1927RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));19281929if (label_size.x > 0.0f)1930RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);19311932IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);1933return value_changed;1934}19351936bool 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)1937{1938ImGuiWindow* window = GetCurrentWindow();1939if (window->SkipItems)1940return false;19411942ImGuiContext& g = *GImGui;1943bool value_changed = false;1944BeginGroup();1945PushID(label);1946PushMultiItemsWidths(components);1947size_t type_size = GDataTypeInfo[data_type].Size;1948for (int i = 0; i < components; i++)1949{1950PushID(i);1951value_changed |= DragScalar("", data_type, v, v_speed, v_min, v_max, format, power);1952SameLine(0, g.Style.ItemInnerSpacing.x);1953PopID();1954PopItemWidth();1955v = (void*)((char*)v + type_size);1956}1957PopID();19581959TextUnformatted(label, FindRenderedTextEnd(label));1960EndGroup();1961return value_changed;1962}19631964bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)1965{1966return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);1967}19681969bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)1970{1971return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);1972}19731974bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)1975{1976return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);1977}19781979bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)1980{1981return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);1982}19831984bool 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)1985{1986ImGuiWindow* window = GetCurrentWindow();1987if (window->SkipItems)1988return false;19891990ImGuiContext& g = *GImGui;1991PushID(label);1992BeginGroup();1993PushMultiItemsWidths(2);19941995bool 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);1996PopItemWidth();1997SameLine(0, g.Style.ItemInnerSpacing.x);1998value_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);1999PopItemWidth();2000SameLine(0, g.Style.ItemInnerSpacing.x);20012002TextUnformatted(label, FindRenderedTextEnd(label));2003EndGroup();2004PopID();2005return value_changed;2006}20072008// NB: v_speed is float to allow adjusting the drag speed with more precision2009bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)2010{2011return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);2012}20132014bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)2015{2016return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);2017}20182019bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)2020{2021return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);2022}20232024bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)2025{2026return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);2027}20282029bool 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)2030{2031ImGuiWindow* window = GetCurrentWindow();2032if (window->SkipItems)2033return false;20342035ImGuiContext& g = *GImGui;2036PushID(label);2037BeginGroup();2038PushMultiItemsWidths(2);20392040bool 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);2041PopItemWidth();2042SameLine(0, g.Style.ItemInnerSpacing.x);2043value_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);2044PopItemWidth();2045SameLine(0, g.Style.ItemInnerSpacing.x);20462047TextUnformatted(label, FindRenderedTextEnd(label));2048EndGroup();2049PopID();20502051return value_changed;2052}20532054//-------------------------------------------------------------------------2055// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.2056//-------------------------------------------------------------------------2057// - SliderBehaviorT<>() [Internal]2058// - SliderBehavior() [Internal]2059// - SliderScalar()2060// - SliderScalarN()2061// - SliderFloat()2062// - SliderFloat2()2063// - SliderFloat3()2064// - SliderFloat4()2065// - SliderAngle()2066// - SliderInt()2067// - SliderInt2()2068// - SliderInt3()2069// - SliderInt4()2070// - VSliderScalar()2071// - VSliderFloat()2072// - VSliderInt()2073//-------------------------------------------------------------------------20742075template<typename TYPE, typename FLOATTYPE>2076float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)2077{2078if (v_min == v_max)2079return 0.0f;20802081const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);2082const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);2083if (is_power)2084{2085if (v_clamped < 0.0f)2086{2087const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));2088return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;2089}2090else2091{2092const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));2093return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);2094}2095}20962097// Linear slider2098return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));2099}21002101// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.2102template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>2103bool 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)2104{2105ImGuiContext& g = *GImGui;2106const ImGuiStyle& style = g.Style;21072108const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;2109const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);2110const bool is_power = (power != 1.0f) && is_decimal;21112112const float grab_padding = 2.0f;2113const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;2114float grab_sz = style.GrabMinSize;2115SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);2116if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows2117grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit2118grab_sz = ImMin(grab_sz, slider_sz);2119const float slider_usable_sz = slider_sz - grab_sz;2120const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz*0.5f;2121const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz*0.5f;21222123// For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f2124float linear_zero_pos; // 0.0->1.0f2125if (is_power && v_min * v_max < 0.0f)2126{2127// Different sign2128const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);2129const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);2130linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));2131}2132else2133{2134// Same sign2135linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;2136}21372138// Process interacting with the slider2139bool value_changed = false;2140if (g.ActiveId == id)2141{2142bool set_new_value = false;2143float clicked_t = 0.0f;2144if (g.ActiveIdSource == ImGuiInputSource_Mouse)2145{2146if (!g.IO.MouseDown[0])2147{2148ClearActiveID();2149}2150else2151{2152const float mouse_abs_pos = g.IO.MousePos[axis];2153clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;2154if (axis == ImGuiAxis_Y)2155clicked_t = 1.0f - clicked_t;2156set_new_value = true;2157}2158}2159else if (g.ActiveIdSource == ImGuiInputSource_Nav)2160{2161const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);2162float delta = (axis == ImGuiAxis_X) ? delta2.x : -delta2.y;2163if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)2164{2165ClearActiveID();2166}2167else if (delta != 0.0f)2168{2169clicked_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);2170const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;2171if ((decimal_precision > 0) || is_power)2172{2173delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds2174if (IsNavInputDown(ImGuiNavInput_TweakSlow))2175delta /= 10.0f;2176}2177else2178{2179if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))2180delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps2181else2182delta /= 100.0f;2183}2184if (IsNavInputDown(ImGuiNavInput_TweakFast))2185delta *= 10.0f;2186set_new_value = true;2187if ((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 limits2188set_new_value = false;2189else2190clicked_t = ImSaturate(clicked_t + delta);2191}2192}21932194if (set_new_value)2195{2196TYPE v_new;2197if (is_power)2198{2199// Account for power curve scale on both sides of the zero2200if (clicked_t < linear_zero_pos)2201{2202// Negative: rescale to the negative range before powering2203float a = 1.0f - (clicked_t / linear_zero_pos);2204a = ImPow(a, power);2205v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);2206}2207else2208{2209// Positive: rescale to the positive range before powering2210float a;2211if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)2212a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);2213else2214a = clicked_t;2215a = ImPow(a, power);2216v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);2217}2218}2219else2220{2221// Linear slider2222if (is_decimal)2223{2224v_new = ImLerp(v_min, v_max, clicked_t);2225}2226else2227{2228// For integer values we want the clicking position to match the grab box so we round above2229// This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..2230FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;2231TYPE v_new_off_floor = (TYPE)(v_new_off_f);2232TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);2233if (!is_decimal && v_new_off_floor < v_new_off_round)2234v_new = v_min + v_new_off_round;2235else2236v_new = v_min + v_new_off_floor;2237}2238}22392240// Round to user desired precision based on format string2241v_new = RoundScalarWithFormatT<TYPE,SIGNEDTYPE>(format, data_type, v_new);22422243// Apply result2244if (*v != v_new)2245{2246*v = v_new;2247value_changed = true;2248}2249}2250}22512252// Output grab position so it can be displayed by the caller2253float grab_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);2254if (axis == ImGuiAxis_Y)2255grab_t = 1.0f - grab_t;2256const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);2257if (axis == ImGuiAxis_X)2258*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);2259else2260*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);22612262return value_changed;2263}22642265// For 32-bits and larger types, slider bounds are limited to half the natural type range.2266// 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.2267// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.2268bool 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)2269{2270switch (data_type)2271{2272case ImGuiDataType_S32:2273IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);2274return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v, *(const ImS32*)v_min, *(const ImS32*)v_max, format, power, flags, out_grab_bb);2275case ImGuiDataType_U32:2276IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);2277return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v, *(const ImU32*)v_min, *(const ImU32*)v_max, format, power, flags, out_grab_bb);2278case ImGuiDataType_S64:2279IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);2280return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v, *(const ImS64*)v_min, *(const ImS64*)v_max, format, power, flags, out_grab_bb);2281case ImGuiDataType_U64:2282IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);2283return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v, *(const ImU64*)v_min, *(const ImU64*)v_max, format, power, flags, out_grab_bb);2284case ImGuiDataType_Float:2285IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);2286return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v, *(const float*)v_min, *(const float*)v_max, format, power, flags, out_grab_bb);2287case ImGuiDataType_Double:2288IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);2289return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb);2290case ImGuiDataType_COUNT: break;2291}2292IM_ASSERT(0);2293return false;2294}22952296bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)2297{2298ImGuiWindow* window = GetCurrentWindow();2299if (window->SkipItems)2300return false;23012302ImGuiContext& g = *GImGui;2303const ImGuiStyle& style = g.Style;2304const ImGuiID id = window->GetID(label);2305const float w = CalcItemWidth();23062307const ImVec2 label_size = CalcTextSize(label, NULL, true);2308const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));2309const 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));23102311ItemSize(total_bb, style.FramePadding.y);2312if (!ItemAdd(total_bb, id, &frame_bb))2313return false;23142315// Default format string when passing NULL2316// Patch old "%.0f" format string to use "%d", read function comments for more details.2317IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);2318if (format == NULL)2319format = GDataTypeInfo[data_type].PrintFmt;2320else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)2321format = PatchFormatStringFloatToInt(format);23222323// Tabbing or CTRL-clicking on Slider turns it into an input box2324bool start_text_input = false;2325const bool tab_focus_requested = FocusableItemRegister(window, id);2326const bool hovered = ItemHoverable(frame_bb, id);2327if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))2328{2329SetActiveID(id, window);2330SetFocusID(id, window);2331FocusWindow(window);2332g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);2333if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)2334{2335start_text_input = true;2336g.ScalarAsInputTextId = 0;2337}2338}2339if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))2340{2341window->DC.CursorPos = frame_bb.Min;2342FocusableItemUnregister(window);2343return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);2344}23452346// Draw frame2347const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);2348RenderNavHighlight(frame_bb, id);2349RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);23502351// Slider behavior2352ImRect grab_bb;2353const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb);2354if (value_changed)2355MarkItemEdited(id);23562357// Render grab2358window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);23592360// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.2361char value_buf[64];2362const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);2363RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));23642365if (label_size.x > 0.0f)2366RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);23672368IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);2369return value_changed;2370}23712372// Add multiple sliders on 1 line for compact edition of multiple components2373bool 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)2374{2375ImGuiWindow* window = GetCurrentWindow();2376if (window->SkipItems)2377return false;23782379ImGuiContext& g = *GImGui;2380bool value_changed = false;2381BeginGroup();2382PushID(label);2383PushMultiItemsWidths(components);2384size_t type_size = GDataTypeInfo[data_type].Size;2385for (int i = 0; i < components; i++)2386{2387PushID(i);2388value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, power);2389SameLine(0, g.Style.ItemInnerSpacing.x);2390PopID();2391PopItemWidth();2392v = (void*)((char*)v + type_size);2393}2394PopID();23952396TextUnformatted(label, FindRenderedTextEnd(label));2397EndGroup();2398return value_changed;2399}24002401bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)2402{2403return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);2404}24052406bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)2407{2408return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);2409}24102411bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)2412{2413return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);2414}24152416bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)2417{2418return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);2419}24202421bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format)2422{2423if (format == NULL)2424format = "%.0f deg";2425float v_deg = (*v_rad) * 360.0f / (2*IM_PI);2426bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, 1.0f);2427*v_rad = v_deg * (2*IM_PI) / 360.0f;2428return value_changed;2429}24302431bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)2432{2433return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);2434}24352436bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)2437{2438return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);2439}24402441bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)2442{2443return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);2444}24452446bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)2447{2448return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);2449}24502451bool 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)2452{2453ImGuiWindow* window = GetCurrentWindow();2454if (window->SkipItems)2455return false;24562457ImGuiContext& g = *GImGui;2458const ImGuiStyle& style = g.Style;2459const ImGuiID id = window->GetID(label);24602461const ImVec2 label_size = CalcTextSize(label, NULL, true);2462const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);2463const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));24642465ItemSize(bb, style.FramePadding.y);2466if (!ItemAdd(frame_bb, id))2467return false;24682469// Default format string when passing NULL2470// Patch old "%.0f" format string to use "%d", read function comments for more details.2471IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);2472if (format == NULL)2473format = GDataTypeInfo[data_type].PrintFmt;2474else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)2475format = PatchFormatStringFloatToInt(format);24762477const bool hovered = ItemHoverable(frame_bb, id);2478if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)2479{2480SetActiveID(id, window);2481SetFocusID(id, window);2482FocusWindow(window);2483g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);2484}24852486// Draw frame2487const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);2488RenderNavHighlight(frame_bb, id);2489RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);24902491// Slider behavior2492ImRect grab_bb;2493const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);2494if (value_changed)2495MarkItemEdited(id);24962497// Render grab2498window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);24992500// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.2501// For the vertical slider we allow centered text to overlap the frame padding2502char value_buf[64];2503const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);2504RenderTextClipped(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));2505if (label_size.x > 0.0f)2506RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);25072508return value_changed;2509}25102511bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)2512{2513return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);2514}25152516bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)2517{2518return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);2519}25202521//-------------------------------------------------------------------------2522// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.2523//-------------------------------------------------------------------------2524// - ImParseFormatFindStart() [Internal]2525// - ImParseFormatFindEnd() [Internal]2526// - ImParseFormatTrimDecorations() [Internal]2527// - ImParseFormatPrecision() [Internal]2528// - InputScalarAsWidgetReplacement() [Internal]2529// - InputScalar()2530// - InputScalarN()2531// - InputFloat()2532// - InputFloat2()2533// - InputFloat3()2534// - InputFloat4()2535// - InputInt()2536// - InputInt2()2537// - InputInt3()2538// - InputInt4()2539// - InputDouble()2540//-------------------------------------------------------------------------25412542// We don't use strchr() because our strings are usually very short and often start with '%'2543const char* ImParseFormatFindStart(const char* fmt)2544{2545while (char c = fmt[0])2546{2547if (c == '%' && fmt[1] != '%')2548return fmt;2549else if (c == '%')2550fmt++;2551fmt++;2552}2553return fmt;2554}25552556const char* ImParseFormatFindEnd(const char* fmt)2557{2558// Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.2559if (fmt[0] != '%')2560return fmt;2561const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));2562const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));2563for (char c; (c = *fmt) != 0; fmt++)2564{2565if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)2566return fmt + 1;2567if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)2568return fmt + 1;2569}2570return fmt;2571}25722573// Extract the format out of a format string with leading or trailing decorations2574// fmt = "blah blah" -> return fmt2575// fmt = "%.3f" -> return fmt2576// fmt = "hello %.3f" -> return fmt + 62577// fmt = "%.3f hello" -> return buf written with "%.3f"2578const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)2579{2580const char* fmt_start = ImParseFormatFindStart(fmt);2581if (fmt_start[0] != '%')2582return fmt;2583const char* fmt_end = ImParseFormatFindEnd(fmt_start);2584if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.2585return fmt_start;2586ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));2587return buf;2588}25892590// Parse display precision back from the display format string2591// 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.2592int ImParseFormatPrecision(const char* fmt, int default_precision)2593{2594fmt = ImParseFormatFindStart(fmt);2595if (fmt[0] != '%')2596return default_precision;2597fmt++;2598while (*fmt >= '0' && *fmt <= '9')2599fmt++;2600int precision = INT_MAX;2601if (*fmt == '.')2602{2603fmt = ImAtoi<int>(fmt + 1, &precision);2604if (precision < 0 || precision > 99)2605precision = default_precision;2606}2607if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation2608precision = -1;2609if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)2610precision = -1;2611return (precision == INT_MAX) ? default_precision : precision;2612}26132614// Create text input in place of an active drag/slider (used when doing a CTRL+Click on drag/slider widgets)2615// FIXME: Facilitate using this in variety of other situations.2616bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)2617{2618ImGuiContext& g = *GImGui;26192620// On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id.2621// We clear ActiveID on the first frame to allow the InputText() taking it back.2622if (g.ScalarAsInputTextId == 0)2623ClearActiveID();26242625char fmt_buf[32];2626char data_buf[32];2627format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));2628DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);2629ImStrTrimBlanks(data_buf);2630ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);2631bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);2632if (g.ScalarAsInputTextId == 0)2633{2634// First frame we started displaying the InputText widget, we expect it to take the active id.2635IM_ASSERT(g.ActiveId == id);2636g.ScalarAsInputTextId = g.ActiveId;2637}2638if (value_changed)2639return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);2640return false;2641}26422643bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags flags)2644{2645ImGuiWindow* window = GetCurrentWindow();2646if (window->SkipItems)2647return false;26482649ImGuiContext& g = *GImGui;2650const ImGuiStyle& style = g.Style;26512652IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);2653if (format == NULL)2654format = GDataTypeInfo[data_type].PrintFmt;26552656char buf[64];2657DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);26582659bool value_changed = false;2660if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)2661flags |= ImGuiInputTextFlags_CharsDecimal;2662flags |= ImGuiInputTextFlags_AutoSelectAll;26632664if (step != NULL)2665{2666const float button_size = GetFrameHeight();26672668BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()2669PushID(label);2670PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));2671if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view2672value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);2673PopItemWidth();26742675// Step buttons2676ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;2677if (flags & ImGuiInputTextFlags_ReadOnly)2678button_flags |= ImGuiButtonFlags_Disabled;2679SameLine(0, style.ItemInnerSpacing.x);2680if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))2681{2682DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);2683value_changed = true;2684}2685SameLine(0, style.ItemInnerSpacing.x);2686if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))2687{2688DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);2689value_changed = true;2690}2691SameLine(0, style.ItemInnerSpacing.x);2692TextUnformatted(label, FindRenderedTextEnd(label));26932694PopID();2695EndGroup();2696}2697else2698{2699if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))2700value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);2701}27022703return value_changed;2704}27052706bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags flags)2707{2708ImGuiWindow* window = GetCurrentWindow();2709if (window->SkipItems)2710return false;27112712ImGuiContext& g = *GImGui;2713bool value_changed = false;2714BeginGroup();2715PushID(label);2716PushMultiItemsWidths(components);2717size_t type_size = GDataTypeInfo[data_type].Size;2718for (int i = 0; i < components; i++)2719{2720PushID(i);2721value_changed |= InputScalar("", data_type, v, step, step_fast, format, flags);2722SameLine(0, g.Style.ItemInnerSpacing.x);2723PopID();2724PopItemWidth();2725v = (void*)((char*)v + type_size);2726}2727PopID();27282729TextUnformatted(label, FindRenderedTextEnd(label));2730EndGroup();2731return value_changed;2732}27332734bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)2735{2736flags |= ImGuiInputTextFlags_CharsScientific;2737return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, flags);2738}27392740bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)2741{2742return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);2743}27442745bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)2746{2747return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);2748}27492750bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)2751{2752return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);2753}27542755// Prefer using "const char* format" directly, which is more flexible and consistent with other API.2756#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS2757bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags flags)2758{2759char format[16] = "%f";2760if (decimal_precision >= 0)2761ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);2762return InputFloat(label, v, step, step_fast, format, flags);2763}27642765bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags flags)2766{2767char format[16] = "%f";2768if (decimal_precision >= 0)2769ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);2770return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);2771}27722773bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags flags)2774{2775char format[16] = "%f";2776if (decimal_precision >= 0)2777ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);2778return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);2779}27802781bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags flags)2782{2783char format[16] = "%f";2784if (decimal_precision >= 0)2785ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);2786return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);2787}2788#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS27892790bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)2791{2792// 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.2793const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";2794return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, flags);2795}27962797bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)2798{2799return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);2800}28012802bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)2803{2804return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);2805}28062807bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)2808{2809return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);2810}28112812bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)2813{2814flags |= ImGuiInputTextFlags_CharsScientific;2815return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, flags);2816}28172818//-------------------------------------------------------------------------2819// [SECTION] Widgets: InputText, InputTextMultiline2820//-------------------------------------------------------------------------2821// - InputText()2822// - InputTextMultiline()2823// - InputTextEx() [Internal]2824//-------------------------------------------------------------------------28252826bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)2827{2828IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()2829return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);2830}28312832bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)2833{2834return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);2835}28362837static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)2838{2839int line_count = 0;2840const char* s = text_begin;2841while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding2842if (c == '\n')2843line_count++;2844s--;2845if (s[0] != '\n' && s[0] != '\r')2846line_count++;2847*out_text_end = s;2848return line_count;2849}28502851static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)2852{2853ImGuiContext& g = *GImGui;2854ImFont* font = g.Font;2855const float line_height = g.FontSize;2856const float scale = line_height / font->FontSize;28572858ImVec2 text_size = ImVec2(0,0);2859float line_width = 0.0f;28602861const ImWchar* s = text_begin;2862while (s < text_end)2863{2864unsigned int c = (unsigned int)(*s++);2865if (c == '\n')2866{2867text_size.x = ImMax(text_size.x, line_width);2868text_size.y += line_height;2869line_width = 0.0f;2870if (stop_on_new_line)2871break;2872continue;2873}2874if (c == '\r')2875continue;28762877const float char_width = font->GetCharAdvance((ImWchar)c) * scale;2878line_width += char_width;2879}28802881if (text_size.x < line_width)2882text_size.x = line_width;28832884if (out_offset)2885*out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n28862887if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n2888text_size.y += line_height;28892890if (remaining)2891*remaining = s;28922893return text_size;2894}28952896// 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)2897namespace ImGuiStb2898{28992900static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }2901static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; }2902static 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); }2903static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }2904static ImWchar STB_TEXTEDIT_NEWLINE = '\n';2905static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)2906{2907const ImWchar* text = obj->TextW.Data;2908const ImWchar* text_remaining = NULL;2909const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);2910r->x0 = 0.0f;2911r->x1 = size.x;2912r->baseline_y_delta = size.y;2913r->ymin = 0.0f;2914r->ymax = size.y;2915r->num_chars = (int)(text_remaining - (text + line_start_idx));2916}29172918static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }2919static 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; }2920static 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; }2921#ifdef __APPLE__ // FIXME: Move setting to IO structure2922static 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; }2923static 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; }2924#else2925static 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; }2926#endif2927#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h2928#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL29292930static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)2931{2932ImWchar* dst = obj->TextW.Data + pos;29332934// We maintain our buffer length in both UTF-8 and wchar formats2935obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);2936obj->CurLenW -= n;29372938// Offset remaining text (FIXME-OPT: Use memmove)2939const ImWchar* src = obj->TextW.Data + pos + n;2940while (ImWchar c = *src++)2941*dst++ = c;2942*dst = '\0';2943}29442945static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)2946{2947const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;2948const int text_len = obj->CurLenW;2949IM_ASSERT(pos <= text_len);29502951const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);2952if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))2953return false;29542955// Grow internal buffer if needed2956if (new_text_len + text_len + 1 > obj->TextW.Size)2957{2958if (!is_resizable)2959return false;2960IM_ASSERT(text_len < obj->TextW.Size);2961obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);2962}29632964ImWchar* text = obj->TextW.Data;2965if (pos != text_len)2966memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));2967memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));29682969obj->CurLenW += new_text_len;2970obj->CurLenA += new_text_len_utf8;2971obj->TextW[obj->CurLenW] = '\0';29722973return true;2974}29752976// 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)2977#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left2978#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right2979#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up2980#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down2981#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line2982#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line2983#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text2984#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text2985#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor2986#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor2987#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo2988#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo2989#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word2990#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word2991#define STB_TEXTEDIT_K_SHIFT 0x2000029922993#define STB_TEXTEDIT_IMPLEMENTATION2994#include "imstb_textedit.h"29952996}29972998void ImGuiInputTextState::OnKeyPressed(int key)2999{3000stb_textedit_key(this, &StbState, key);3001CursorFollow = true;3002CursorAnimReset();3003}30043005ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()3006{3007memset(this, 0, sizeof(*this));3008}30093010// Public API to manipulate UTF-8 text3011// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)3012// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.3013void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)3014{3015IM_ASSERT(pos + bytes_count <= BufTextLen);3016char* dst = Buf + pos;3017const char* src = Buf + pos + bytes_count;3018while (char c = *src++)3019*dst++ = c;3020*dst = '\0';30213022if (CursorPos + bytes_count >= pos)3023CursorPos -= bytes_count;3024else if (CursorPos >= pos)3025CursorPos = pos;3026SelectionStart = SelectionEnd = CursorPos;3027BufDirty = true;3028BufTextLen -= bytes_count;3029}30303031void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)3032{3033const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;3034const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);3035if (new_text_len + BufTextLen >= BufSize)3036{3037if (!is_resizable)3038return;30393040// Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)3041ImGuiContext& g = *GImGui;3042ImGuiInputTextState* edit_state = &g.InputTextState;3043IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);3044IM_ASSERT(Buf == edit_state->TempBuffer.Data);3045int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;3046edit_state->TempBuffer.reserve(new_buf_size + 1);3047Buf = edit_state->TempBuffer.Data;3048BufSize = edit_state->BufCapacityA = new_buf_size;3049}30503051if (BufTextLen != pos)3052memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));3053memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));3054Buf[BufTextLen + new_text_len] = '\0';30553056if (CursorPos >= pos)3057CursorPos += new_text_len;3058SelectionStart = SelectionEnd = CursorPos;3059BufDirty = true;3060BufTextLen += new_text_len;3061}30623063// Return false to discard a character.3064static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)3065{3066unsigned int c = *p_char;30673068if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))3069{3070bool pass = false;3071pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));3072pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));3073if (!pass)3074return false;3075}30763077if (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.3078return false;30793080if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))3081{3082if (flags & ImGuiInputTextFlags_CharsDecimal)3083if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))3084return false;30853086if (flags & ImGuiInputTextFlags_CharsScientific)3087if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))3088return false;30893090if (flags & ImGuiInputTextFlags_CharsHexadecimal)3091if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))3092return false;30933094if (flags & ImGuiInputTextFlags_CharsUppercase)3095if (c >= 'a' && c <= 'z')3096*p_char = (c += (unsigned int)('A'-'a'));30973098if (flags & ImGuiInputTextFlags_CharsNoBlank)3099if (ImCharIsBlankW(c))3100return false;3101}31023103if (flags & ImGuiInputTextFlags_CallbackCharFilter)3104{3105ImGuiInputTextCallbackData callback_data;3106memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));3107callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;3108callback_data.EventChar = (ImWchar)c;3109callback_data.Flags = flags;3110callback_data.UserData = user_data;3111if (callback(&callback_data) != 0)3112return false;3113*p_char = callback_data.EventChar;3114if (!callback_data.EventChar)3115return false;3116}31173118return true;3119}31203121// Edit a string of text3122// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".3123// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match3124// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.3125// - 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.3126// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h3127// (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)3128bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)3129{3130ImGuiWindow* window = GetCurrentWindow();3131if (window->SkipItems)3132return false;31333134IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)3135IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)31363137ImGuiContext& g = *GImGui;3138ImGuiIO& io = g.IO;3139const ImGuiStyle& style = g.Style;31403141const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;3142const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;3143const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;3144const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;3145const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;3146if (is_resizable)3147IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!31483149if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,3150BeginGroup();3151const ImGuiID id = window->GetID(label);3152const ImVec2 label_size = CalcTextSize(label, NULL, true);3153ImVec2 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-line3154const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);3155const 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));31563157ImGuiWindow* draw_window = window;3158if (is_multiline)3159{3160if (!ItemAdd(total_bb, id, &frame_bb))3161{3162ItemSize(total_bb, style.FramePadding.y);3163EndGroup();3164return false;3165}3166if (!BeginChildFrame(id, frame_bb.GetSize()))3167{3168EndChildFrame();3169EndGroup();3170return false;3171}3172draw_window = GetCurrentWindow();3173draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight3174size.x -= draw_window->ScrollbarSizes.x;3175}3176else3177{3178ItemSize(total_bb, style.FramePadding.y);3179if (!ItemAdd(total_bb, id, &frame_bb))3180return false;3181}3182const bool hovered = ItemHoverable(frame_bb, id);3183if (hovered)3184g.MouseCursor = ImGuiMouseCursor_TextInput;31853186// Password pushes a temporary font with only a fallback glyph3187if (is_password)3188{3189const ImFontGlyph* glyph = g.Font->FindGlyph('*');3190ImFont* password_font = &g.InputTextPasswordFont;3191password_font->FontSize = g.Font->FontSize;3192password_font->Scale = g.Font->Scale;3193password_font->DisplayOffset = g.Font->DisplayOffset;3194password_font->Ascent = g.Font->Ascent;3195password_font->Descent = g.Font->Descent;3196password_font->ContainerAtlas = g.Font->ContainerAtlas;3197password_font->FallbackGlyph = glyph;3198password_font->FallbackAdvanceX = glyph->AdvanceX;3199IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());3200PushFont(password_font);3201}32023203// NB: we are only allowed to access 'edit_state' if we are the active widget.3204ImGuiInputTextState& edit_state = g.InputTextState;32053206const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing3207const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);3208const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;32093210const bool user_clicked = hovered && io.MouseClicked[0];3211const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");3212const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));32133214bool clear_active_id = false;32153216bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);3217if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)3218{3219if (g.ActiveId != id)3220{3221// Start edition3222// Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)3223// From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)3224const int prev_len_w = edit_state.CurLenW;3225const int init_buf_len = (int)strlen(buf);3226edit_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.3227edit_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.3228memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);3229const char* buf_end = NULL;3230edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);3231edit_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.3232edit_state.CursorAnimReset();32333234// Preserve cursor position and undo/redo stack if we come back to same widget3235// FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).3236const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);3237if (recycle_state)3238{3239// Recycle existing cursor/selection/undo stack but clamp position3240// Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.3241edit_state.CursorClamp();3242}3243else3244{3245edit_state.ID = id;3246edit_state.ScrollX = 0.0f;3247stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);3248if (!is_multiline && focus_requested_by_code)3249select_all = true;3250}3251if (flags & ImGuiInputTextFlags_AlwaysInsertMode)3252edit_state.StbState.insert_mode = 1;3253if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))3254select_all = true;3255}3256SetActiveID(id, window);3257SetFocusID(id, window);3258FocusWindow(window);3259g.ActiveIdBlockNavInputFlags = (1 << ImGuiNavInput_Cancel);3260if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))3261g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));3262}3263else if (io.MouseClicked[0])3264{3265// Release focus when we click outside3266clear_active_id = true;3267}32683269bool value_changed = false;3270bool enter_pressed = false;3271int backup_current_text_length = 0;32723273if (g.ActiveId == id)3274{3275if (!is_editable && !g.ActiveIdIsJustActivated)3276{3277// When read-only we always use the live data passed to the function3278edit_state.TextW.resize(buf_size+1);3279const char* buf_end = NULL;3280edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);3281edit_state.CurLenA = (int)(buf_end - buf);3282edit_state.CursorClamp();3283}32843285backup_current_text_length = edit_state.CurLenA;3286edit_state.BufCapacityA = buf_size;3287edit_state.UserFlags = flags;3288edit_state.UserCallback = callback;3289edit_state.UserCallbackData = callback_user_data;32903291// Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.3292// Down the line we should have a cleaner library-wide concept of Selected vs Active.3293g.ActiveIdAllowOverlap = !io.MouseDown[0];3294g.WantTextInputNextFrame = 1;32953296// Edit in progress3297const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;3298const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));32993300const bool is_osx = io.ConfigMacOSXBehaviors;3301if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))3302{3303edit_state.SelectAll();3304edit_state.SelectedAllMouseLock = true;3305}3306else if (hovered && is_osx && io.MouseDoubleClicked[0])3307{3308// Double-click select a word only, OS X style (by simulating keystrokes)3309edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);3310edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);3311}3312else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)3313{3314if (hovered)3315{3316stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);3317edit_state.CursorAnimReset();3318}3319}3320else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))3321{3322stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);3323edit_state.CursorAnimReset();3324edit_state.CursorFollow = true;3325}3326if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])3327edit_state.SelectedAllMouseLock = false;33283329if (io.InputQueueCharacters.Size > 0)3330{3331// Process text input (before we check for Return because using some IME will effectively send a Return?)3332// 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.3333bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);3334if (!ignore_inputs && is_editable && !user_nav_input_start)3335for (int n = 0; n < io.InputQueueCharacters.Size; n++)3336{3337// Insert character if they pass filtering3338unsigned int c = (unsigned int)io.InputQueueCharacters[n];3339if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))3340edit_state.OnKeyPressed((int)c);3341}33423343// Consume characters3344io.InputQueueCharacters.resize(0);3345}3346}33473348bool cancel_edit = false;3349if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)3350{3351// Handle key-presses3352const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);3353const bool is_osx = io.ConfigMacOSXBehaviors;3354const 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 Ctrl3355const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;3356const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl3357const 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/End3358const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;3359const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;33603361const 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());3362const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());3363const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;3364const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);3365const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;33663367if (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); }3368else 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); }3369else 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); }3370else 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); }3371else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }3372else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }3373else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }3374else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)3375{3376if (!edit_state.HasSelection())3377{3378if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);3379else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);3380}3381edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);3382}3383else if (IsKeyPressedMap(ImGuiKey_Enter))3384{3385bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;3386if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))3387{3388enter_pressed = clear_active_id = true;3389}3390else if (is_editable)3391{3392unsigned int c = '\n'; // Insert new line3393if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))3394edit_state.OnKeyPressed((int)c);3395}3396}3397else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)3398{3399unsigned int c = '\t'; // Insert TAB3400if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))3401edit_state.OnKeyPressed((int)c);3402}3403else if (IsKeyPressedMap(ImGuiKey_Escape))3404{3405clear_active_id = cancel_edit = true;3406}3407else if (is_undo || is_redo)3408{3409edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);3410edit_state.ClearSelection();3411}3412else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))3413{3414edit_state.SelectAll();3415edit_state.CursorFollow = true;3416}3417else if (is_cut || is_copy)3418{3419// Cut, Copy3420if (io.SetClipboardTextFn)3421{3422const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;3423const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;3424edit_state.TempBuffer.resize((ie-ib) * 4 + 1);3425ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);3426SetClipboardText(edit_state.TempBuffer.Data);3427}3428if (is_cut)3429{3430if (!edit_state.HasSelection())3431edit_state.SelectAll();3432edit_state.CursorFollow = true;3433stb_textedit_cut(&edit_state, &edit_state.StbState);3434}3435}3436else if (is_paste)3437{3438if (const char* clipboard = GetClipboardText())3439{3440// Filter pasted buffer3441const int clipboard_len = (int)strlen(clipboard);3442ImWchar* clipboard_filtered = (ImWchar*)MemAlloc((clipboard_len+1) * sizeof(ImWchar));3443int clipboard_filtered_len = 0;3444for (const char* s = clipboard; *s; )3445{3446unsigned int c;3447s += ImTextCharFromUtf8(&c, s, NULL);3448if (c == 0)3449break;3450if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))3451continue;3452clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;3453}3454clipboard_filtered[clipboard_filtered_len] = 0;3455if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation3456{3457stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);3458edit_state.CursorFollow = true;3459}3460MemFree(clipboard_filtered);3461}3462}3463}34643465if (g.ActiveId == id)3466{3467const char* apply_new_text = NULL;3468int apply_new_text_length = 0;3469if (cancel_edit)3470{3471// Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.3472if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)3473{3474apply_new_text = edit_state.InitialText.Data;3475apply_new_text_length = edit_state.InitialText.Size - 1;3476}3477}34783479// 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.3480// 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.3481bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);3482if (apply_edit_back_to_user_buffer)3483{3484// Apply new value immediately - copy modified buffer back3485// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer3486// FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.3487// FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.3488if (is_editable)3489{3490edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);3491ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);3492}34933494// User callback3495if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)3496{3497IM_ASSERT(callback != NULL);34983499// The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.3500ImGuiInputTextFlags event_flag = 0;3501ImGuiKey event_key = ImGuiKey_COUNT;3502if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))3503{3504event_flag = ImGuiInputTextFlags_CallbackCompletion;3505event_key = ImGuiKey_Tab;3506}3507else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))3508{3509event_flag = ImGuiInputTextFlags_CallbackHistory;3510event_key = ImGuiKey_UpArrow;3511}3512else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))3513{3514event_flag = ImGuiInputTextFlags_CallbackHistory;3515event_key = ImGuiKey_DownArrow;3516}3517else if (flags & ImGuiInputTextFlags_CallbackAlways)3518event_flag = ImGuiInputTextFlags_CallbackAlways;35193520if (event_flag)3521{3522ImGuiInputTextCallbackData callback_data;3523memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));3524callback_data.EventFlag = event_flag;3525callback_data.Flags = flags;3526callback_data.UserData = callback_user_data;35273528callback_data.EventKey = event_key;3529callback_data.Buf = edit_state.TempBuffer.Data;3530callback_data.BufTextLen = edit_state.CurLenA;3531callback_data.BufSize = edit_state.BufCapacityA;3532callback_data.BufDirty = false;35333534// 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)3535ImWchar* text = edit_state.TextW.Data;3536const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);3537const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);3538const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);35393540// Call user code3541callback(&callback_data);35423543// Read back what user may have modified3544IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields3545IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);3546IM_ASSERT(callback_data.Flags == flags);3547if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }3548if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }3549if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }3550if (callback_data.BufDirty)3551{3552IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!3553if (callback_data.BufTextLen > backup_current_text_length && is_resizable)3554edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));3555edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);3556edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()3557edit_state.CursorAnimReset();3558}3559}3560}35613562// Will copy result string if modified3563if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)3564{3565apply_new_text = edit_state.TempBuffer.Data;3566apply_new_text_length = edit_state.CurLenA;3567}3568}35693570// Copy result to user buffer3571if (apply_new_text)3572{3573IM_ASSERT(apply_new_text_length >= 0);3574if (backup_current_text_length != apply_new_text_length && is_resizable)3575{3576ImGuiInputTextCallbackData callback_data;3577callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;3578callback_data.Flags = flags;3579callback_data.Buf = buf;3580callback_data.BufTextLen = apply_new_text_length;3581callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);3582callback_data.UserData = callback_user_data;3583callback(&callback_data);3584buf = callback_data.Buf;3585buf_size = callback_data.BufSize;3586apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);3587IM_ASSERT(apply_new_text_length <= buf_size);3588}35893590// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.3591ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));3592value_changed = true;3593}35943595// Clear temporary user storage3596edit_state.UserFlags = 0;3597edit_state.UserCallback = NULL;3598edit_state.UserCallbackData = NULL;3599}36003601// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)3602if (clear_active_id && g.ActiveId == id)3603ClearActiveID();36043605// Render3606// 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.3607const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;36083609// Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line3610// without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.3611// Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.3612const int buf_display_max_length = 2 * 1024 * 1024;36133614if (!is_multiline)3615{3616RenderNavHighlight(frame_bb, id);3617RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);3618}36193620const 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 size3621ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;3622ImVec2 text_size(0.f, 0.f);3623const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));3624if (g.ActiveId == id || is_currently_scrolling)3625{3626edit_state.CursorAnim += io.DeltaTime;36273628// This is going to be messy. We need to:3629// - Display the text (this alone can be more easily clipped)3630// - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)3631// - Measure text height (for scrollbar)3632// 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)3633// FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.3634const ImWchar* text_begin = edit_state.TextW.Data;3635ImVec2 cursor_offset, select_start_offset;36363637{3638// Count lines + find lines numbers straddling 'cursor' and 'select_start' position.3639const ImWchar* searches_input_ptr[2];3640searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;3641searches_input_ptr[1] = NULL;3642int searches_remaining = 1;3643int searches_result_line_number[2] = { -1, -999 };3644if (edit_state.StbState.select_start != edit_state.StbState.select_end)3645{3646searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);3647searches_result_line_number[1] = -1;3648searches_remaining++;3649}36503651// Iterate all lines to find our line numbers3652// In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.3653searches_remaining += is_multiline ? 1 : 0;3654int line_count = 0;3655//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-bits3656for (const ImWchar* s = text_begin; *s != 0; s++)3657if (*s == '\n')3658{3659line_count++;3660if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }3661if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }3662}3663line_count++;3664if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;3665if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;36663667// Calculate 2d position by finding the beginning of the line and measuring distance3668cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;3669cursor_offset.y = searches_result_line_number[0] * g.FontSize;3670if (searches_result_line_number[1] >= 0)3671{3672select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;3673select_start_offset.y = searches_result_line_number[1] * g.FontSize;3674}36753676// Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)3677if (is_multiline)3678text_size = ImVec2(size.x, line_count * g.FontSize);3679}36803681// Scroll3682if (edit_state.CursorFollow)3683{3684// Horizontal scroll in chunks of quarter width3685if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))3686{3687const float scroll_increment_x = size.x * 0.25f;3688if (cursor_offset.x < edit_state.ScrollX)3689edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);3690else if (cursor_offset.x - size.x >= edit_state.ScrollX)3691edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);3692}3693else3694{3695edit_state.ScrollX = 0.0f;3696}36973698// Vertical scroll3699if (is_multiline)3700{3701float scroll_y = draw_window->Scroll.y;3702if (cursor_offset.y - g.FontSize < scroll_y)3703scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);3704else if (cursor_offset.y - size.y >= scroll_y)3705scroll_y = cursor_offset.y - size.y;3706draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag3707draw_window->Scroll.y = scroll_y;3708render_pos.y = draw_window->DC.CursorPos.y;3709}3710}3711edit_state.CursorFollow = false;3712const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);37133714// Draw selection3715if (edit_state.StbState.select_start != edit_state.StbState.select_end)3716{3717const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);3718const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);37193720float 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.3721float bg_offy_dn = is_multiline ? 0.0f : 2.0f;3722ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);3723ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;3724for (const ImWchar* p = text_selected_begin; p < text_selected_end; )3725{3726if (rect_pos.y > clip_rect.w + g.FontSize)3727break;3728if (rect_pos.y < clip_rect.y)3729{3730//p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bits3731//p = p ? p + 1 : text_selected_end;3732while (p < text_selected_end)3733if (*p++ == '\n')3734break;3735}3736else3737{3738ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);3739if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines3740ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));3741rect.ClipWith(clip_rect);3742if (rect.Overlaps(clip_rect))3743draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);3744}3745rect_pos.x = render_pos.x - render_scroll.x;3746rect_pos.y += g.FontSize;3747}3748}37493750const int buf_display_len = edit_state.CurLenA;3751if (is_multiline || buf_display_len < buf_display_max_length)3752draw_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);37533754// Draw blinking cursor3755bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;3756ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;3757ImRect 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);3758if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))3759draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));37603761// 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.)3762if (is_editable)3763g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);3764}3765else3766{3767// Render text only3768const char* buf_end = NULL;3769if (is_multiline)3770text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width3771else3772buf_end = buf_display + strlen(buf_display);3773if (is_multiline || (buf_end - buf_display) < buf_display_max_length)3774draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);3775}37763777if (is_multiline)3778{3779Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line3780EndChildFrame();3781EndGroup();3782}37833784if (is_password)3785PopFont();37863787// Log as text3788if (g.LogEnabled && !is_password)3789LogRenderedText(&render_pos, buf_display, NULL);37903791if (label_size.x > 0)3792RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);37933794if (value_changed)3795MarkItemEdited(id);37963797IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);3798if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)3799return enter_pressed;3800else3801return value_changed;3802}38033804//-------------------------------------------------------------------------3805// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.3806//-------------------------------------------------------------------------3807// - ColorEdit3()3808// - ColorEdit4()3809// - ColorPicker3()3810// - RenderColorRectWithAlphaCheckerboard() [Internal]3811// - ColorPicker4()3812// - ColorButton()3813// - SetColorEditOptions()3814// - ColorTooltip() [Internal]3815// - ColorEditOptionsPopup() [Internal]3816// - ColorPickerOptionsPopup() [Internal]3817//-------------------------------------------------------------------------38183819bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)3820{3821return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);3822}38233824// Edit colors components (each component in 0.0f..1.0f range).3825// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.3826// 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.3827bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)3828{3829ImGuiWindow* window = GetCurrentWindow();3830if (window->SkipItems)3831return false;38323833ImGuiContext& g = *GImGui;3834const ImGuiStyle& style = g.Style;3835const float square_sz = GetFrameHeight();3836const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);3837const float w_items_all = CalcItemWidth() - w_extra;3838const char* label_display_end = FindRenderedTextEnd(label);38393840BeginGroup();3841PushID(label);38423843// If we're not showing any slider there's no point in doing any HSV conversions3844const ImGuiColorEditFlags flags_untouched = flags;3845if (flags & ImGuiColorEditFlags_NoInputs)3846flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;38473848// Context menu: display and modify options (before defaults are applied)3849if (!(flags & ImGuiColorEditFlags_NoOptions))3850ColorEditOptionsPopup(col, flags);38513852// Read stored options3853if (!(flags & ImGuiColorEditFlags__InputsMask))3854flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);3855if (!(flags & ImGuiColorEditFlags__DataTypeMask))3856flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);3857if (!(flags & ImGuiColorEditFlags__PickerMask))3858flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);3859flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));38603861const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;3862const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;3863const int components = alpha ? 4 : 3;38643865// Convert to the formats we need3866float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };3867if (flags & ImGuiColorEditFlags_HSV)3868ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);3869int 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]) };38703871bool value_changed = false;3872bool value_changed_as_float = false;38733874if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)3875{3876// RGB/HSV 0..255 Sliders3877const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));3878const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));38793880const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);3881const char* ids[4] = { "##X", "##Y", "##Z", "##W" };3882const char* fmt_table_int[3][4] =3883{3884{ "%3d", "%3d", "%3d", "%3d" }, // Short display3885{ "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA3886{ "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA3887};3888const char* fmt_table_float[3][4] =3889{3890{ "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display3891{ "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA3892{ "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA3893};3894const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;38953896PushItemWidth(w_item_one);3897for (int n = 0; n < components; n++)3898{3899if (n > 0)3900SameLine(0, style.ItemInnerSpacing.x);3901if (n + 1 == components)3902PushItemWidth(w_item_last);3903if (flags & ImGuiColorEditFlags_Float)3904{3905value_changed |= DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);3906value_changed_as_float |= value_changed;3907}3908else3909{3910value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);3911}3912if (!(flags & ImGuiColorEditFlags_NoOptions))3913OpenPopupOnItemClick("context");3914}3915PopItemWidth();3916PopItemWidth();3917}3918else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)3919{3920// RGB Hexadecimal Input3921char buf[64];3922if (alpha)3923ImFormatString(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));3924else3925ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));3926PushItemWidth(w_items_all);3927if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))3928{3929value_changed = true;3930char* p = buf;3931while (*p == '#' || ImCharIsBlankA(*p))3932p++;3933i[0] = i[1] = i[2] = i[3] = 0;3934if (alpha)3935sscanf(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)3936else3937sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);3938}3939if (!(flags & ImGuiColorEditFlags_NoOptions))3940OpenPopupOnItemClick("context");3941PopItemWidth();3942}39433944ImGuiWindow* picker_active_window = NULL;3945if (!(flags & ImGuiColorEditFlags_NoSmallPreview))3946{3947if (!(flags & ImGuiColorEditFlags_NoInputs))3948SameLine(0, style.ItemInnerSpacing.x);39493950const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);3951if (ColorButton("##ColorButton", col_v4, flags))3952{3953if (!(flags & ImGuiColorEditFlags_NoPicker))3954{3955// Store current color and open a picker3956g.ColorPickerRef = col_v4;3957OpenPopup("picker");3958SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));3959}3960}3961if (!(flags & ImGuiColorEditFlags_NoOptions))3962OpenPopupOnItemClick("context");39633964if (BeginPopup("picker"))3965{3966picker_active_window = g.CurrentWindow;3967if (label != label_display_end)3968{3969TextUnformatted(label, label_display_end);3970Spacing();3971}3972ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;3973ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;3974PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?3975value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);3976PopItemWidth();3977EndPopup();3978}3979}39803981if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))3982{3983SameLine(0, style.ItemInnerSpacing.x);3984TextUnformatted(label, label_display_end);3985}39863987// Convert back3988if (picker_active_window == NULL)3989{3990if (!value_changed_as_float)3991for (int n = 0; n < 4; n++)3992f[n] = i[n] / 255.0f;3993if (flags & ImGuiColorEditFlags_HSV)3994ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);3995if (value_changed)3996{3997col[0] = f[0];3998col[1] = f[1];3999col[2] = f[2];4000if (alpha)4001col[3] = f[3];4002}4003}40044005PopID();4006EndGroup();40074008// Drag and Drop Target4009// NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.4010if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())4011{4012if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))4013{4014memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V5124015value_changed = true;4016}4017if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))4018{4019memcpy((float*)col, payload->Data, sizeof(float) * components);4020value_changed = true;4021}4022EndDragDropTarget();4023}40244025// When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().4026if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)4027window->DC.LastItemId = g.ActiveId;40284029if (value_changed)4030MarkItemEdited(window->DC.LastItemId);40314032return value_changed;4033}40344035bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)4036{4037float col4[4] = { col[0], col[1], col[2], 1.0f };4038if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))4039return false;4040col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];4041return true;4042}40434044static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)4045{4046float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;4047int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);4048int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);4049int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);4050return IM_COL32(r, g, b, 0xFF);4051}40524053// Helper for ColorPicker4()4054// 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.4055// 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.4056void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)4057{4058ImGuiWindow* window = GetCurrentWindow();4059if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)4060{4061ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));4062ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));4063window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);40644065int yi = 0;4066for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)4067{4068float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);4069if (y2 <= y1)4070continue;4071for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)4072{4073float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);4074if (x2 <= x1)4075continue;4076int rounding_corners_flags_cell = 0;4077if (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; }4078if (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; }4079rounding_corners_flags_cell &= rounding_corners_flags;4080window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);4081}4082}4083}4084else4085{4086window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);4087}4088}40894090// Helper for ColorPicker4()4091static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)4092{4093ImGui::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);4094ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);4095ImGui::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);4096ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);4097}40984099// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.4100// 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..)4101bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)4102{4103ImGuiContext& g = *GImGui;4104ImGuiWindow* window = GetCurrentWindow();4105ImDrawList* draw_list = window->DrawList;41064107ImGuiStyle& style = g.Style;4108ImGuiIO& io = g.IO;41094110PushID(label);4111BeginGroup();41124113if (!(flags & ImGuiColorEditFlags_NoSidePreview))4114flags |= ImGuiColorEditFlags_NoSmallPreview;41154116// Context menu: display and store options.4117if (!(flags & ImGuiColorEditFlags_NoOptions))4118ColorPickerOptionsPopup(col, flags);41194120// Read stored options4121if (!(flags & ImGuiColorEditFlags__PickerMask))4122flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;4123IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected4124if (!(flags & ImGuiColorEditFlags_NoOptions))4125flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);41264127// Setup4128int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;4129bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);4130ImVec2 picker_pos = window->DC.CursorPos;4131float square_sz = GetFrameHeight();4132float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars4133float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box4134float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;4135float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;4136float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);41374138float backup_initial_col[4];4139memcpy(backup_initial_col, col, components * sizeof(float));41404141float wheel_thickness = sv_picker_size * 0.08f;4142float wheel_r_outer = sv_picker_size * 0.50f;4143float wheel_r_inner = wheel_r_outer - wheel_thickness;4144ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);41454146// Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.4147float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);4148ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.4149ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.4150ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.41514152float H,S,V;4153ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);41544155bool value_changed = false, value_changed_h = false, value_changed_sv = false;41564157PushItemFlag(ImGuiItemFlags_NoNav, true);4158if (flags & ImGuiColorEditFlags_PickerHueWheel)4159{4160// Hue wheel + SV triangle logic4161InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));4162if (IsItemActive())4163{4164ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;4165ImVec2 current_off = g.IO.MousePos - wheel_center;4166float initial_dist2 = ImLengthSqr(initial_off);4167if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))4168{4169// Interactive with Hue wheel4170H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;4171if (H < 0.0f)4172H += 1.0f;4173value_changed = value_changed_h = true;4174}4175float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);4176float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);4177if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))4178{4179// Interacting with SV triangle4180ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);4181if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))4182current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);4183float uu, vv, ww;4184ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);4185V = ImClamp(1.0f - vv, 0.0001f, 1.0f);4186S = ImClamp(uu / V, 0.0001f, 1.0f);4187value_changed = value_changed_sv = true;4188}4189}4190if (!(flags & ImGuiColorEditFlags_NoOptions))4191OpenPopupOnItemClick("context");4192}4193else if (flags & ImGuiColorEditFlags_PickerHueBar)4194{4195// SV rectangle logic4196InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));4197if (IsItemActive())4198{4199S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));4200V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));4201value_changed = value_changed_sv = true;4202}4203if (!(flags & ImGuiColorEditFlags_NoOptions))4204OpenPopupOnItemClick("context");42054206// Hue bar logic4207SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));4208InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));4209if (IsItemActive())4210{4211H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));4212value_changed = value_changed_h = true;4213}4214}42154216// Alpha bar logic4217if (alpha_bar)4218{4219SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));4220InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));4221if (IsItemActive())4222{4223col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));4224value_changed = true;4225}4226}4227PopItemFlag(); // ImGuiItemFlags_NoNav42284229if (!(flags & ImGuiColorEditFlags_NoSidePreview))4230{4231SameLine(0, style.ItemInnerSpacing.x);4232BeginGroup();4233}42344235if (!(flags & ImGuiColorEditFlags_NoLabel))4236{4237const char* label_display_end = FindRenderedTextEnd(label);4238if (label != label_display_end)4239{4240if ((flags & ImGuiColorEditFlags_NoSidePreview))4241SameLine(0, style.ItemInnerSpacing.x);4242TextUnformatted(label, label_display_end);4243}4244}42454246if (!(flags & ImGuiColorEditFlags_NoSidePreview))4247{4248PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);4249ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);4250if ((flags & ImGuiColorEditFlags_NoLabel))4251Text("Current");4252ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));4253if (ref_col != NULL)4254{4255Text("Original");4256ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);4257if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))4258{4259memcpy(col, ref_col, components * sizeof(float));4260value_changed = true;4261}4262}4263PopItemFlag();4264EndGroup();4265}42664267// Convert back color to RGB4268if (value_changed_h || value_changed_sv)4269ColorConvertHSVtoRGB(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]);42704271// R,G,B and H,S,V slider color editor4272bool value_changed_fix_hue_wrap = false;4273if ((flags & ImGuiColorEditFlags_NoInputs) == 0)4274{4275PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);4276ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;4277ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;4278if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)4279if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB))4280{4281// FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.4282// 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)4283value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);4284value_changed = true;4285}4286if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)4287value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);4288if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)4289value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);4290PopItemWidth();4291}42924293// Try to cancel hue wrap (after ColorEdit4 call), if any4294if (value_changed_fix_hue_wrap)4295{4296float new_H, new_S, new_V;4297ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);4298if (new_H <= 0 && H > 0)4299{4300if (new_V <= 0 && V != new_V)4301ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);4302else if (new_S <= 0)4303ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);4304}4305}43064307ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);4308ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);4309ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));43104311const 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) };4312ImVec2 sv_cursor_pos;43134314if (flags & ImGuiColorEditFlags_PickerHueWheel)4315{4316// Render Hue Wheel4317const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).4318const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);4319for (int n = 0; n < 6; n++)4320{4321const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;4322const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;4323const int vert_start_idx = draw_list->VtxBuffer.Size;4324draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);4325draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);4326const int vert_end_idx = draw_list->VtxBuffer.Size;43274328// Paint colors over existing vertices4329ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);4330ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);4331ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);4332}43334334// Render Cursor + preview on Hue Wheel4335float cos_hue_angle = ImCos(H * 2.0f * IM_PI);4336float sin_hue_angle = ImSin(H * 2.0f * IM_PI);4337ImVec2 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);4338float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;4339int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);4340draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);4341draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);4342draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);43434344// Render SV triangle (rotated according to hue)4345ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);4346ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);4347ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);4348ImVec2 uv_white = GetFontTexUvWhitePixel();4349draw_list->PrimReserve(6, 6);4350draw_list->PrimVtx(tra, uv_white, hue_color32);4351draw_list->PrimVtx(trb, uv_white, hue_color32);4352draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);4353draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);4354draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);4355draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);4356draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);4357sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));4358}4359else if (flags & ImGuiColorEditFlags_PickerHueBar)4360{4361// Render SV Square4362draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);4363draw_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);4364RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);4365sv_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 much4366sv_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);43674368// Render Hue Bar4369for (int i = 0; i < 6; ++i)4370draw_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]);4371float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);4372RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);4373RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);4374}43754376// Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)4377float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;4378draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);4379draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);4380draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);43814382// Render alpha bar4383if (alpha_bar)4384{4385float alpha = ImSaturate(col[3]);4386ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);4387RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));4388draw_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);4389float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);4390RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);4391RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);4392}43934394EndGroup();43954396if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)4397value_changed = false;4398if (value_changed)4399MarkItemEdited(window->DC.LastItemId);44004401PopID();44024403return value_changed;4404}44054406// A little colored square. Return true when clicked.4407// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.4408// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.4409bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)4410{4411ImGuiWindow* window = GetCurrentWindow();4412if (window->SkipItems)4413return false;44144415ImGuiContext& g = *GImGui;4416const ImGuiID id = window->GetID(desc_id);4417float default_size = GetFrameHeight();4418if (size.x == 0.0f)4419size.x = default_size;4420if (size.y == 0.0f)4421size.y = default_size;4422const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);4423ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);4424if (!ItemAdd(bb, id))4425return false;44264427bool hovered, held;4428bool pressed = ButtonBehavior(bb, id, &hovered, &held);44294430if (flags & ImGuiColorEditFlags_NoAlpha)4431flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);44324433ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);4434float grid_step = ImMin(size.x, size.y) / 2.99f;4435float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);4436ImRect bb_inner = bb;4437float 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.4438bb_inner.Expand(off);4439if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)4440{4441float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);4442RenderColorRectWithAlphaCheckerboard(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);4443window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);4444}4445else4446{4447// Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha4448ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;4449if (col_source.w < 1.0f)4450RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);4451else4452window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);4453}4454RenderNavHighlight(bb, id);4455if (g.Style.FrameBorderSize > 0.0f)4456RenderFrameBorder(bb.Min, bb.Max, rounding);4457else4458window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border44594460// Drag and Drop Source4461// NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.4462if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())4463{4464if (flags & ImGuiColorEditFlags_NoAlpha)4465SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);4466else4467SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);4468ColorButton(desc_id, col, flags);4469SameLine();4470TextUnformatted("Color");4471EndDragDropSource();4472}44734474// Tooltip4475if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)4476ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));44774478if (pressed)4479MarkItemEdited(id);44804481return pressed;4482}44834484void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)4485{4486ImGuiContext& g = *GImGui;4487if ((flags & ImGuiColorEditFlags__InputsMask) == 0)4488flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;4489if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)4490flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;4491if ((flags & ImGuiColorEditFlags__PickerMask) == 0)4492flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;4493IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected4494IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected4495IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected4496g.ColorEditOptions = flags;4497}44984499// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.4500void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)4501{4502ImGuiContext& g = *GImGui;45034504int 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]);4505BeginTooltipEx(0, true);45064507const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;4508if (text_end > text)4509{4510TextUnformatted(text, text_end);4511Separator();4512}45134514ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);4515ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);4516SameLine();4517if (flags & ImGuiColorEditFlags_NoAlpha)4518Text("#%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]);4519else4520Text("#%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]);4521EndTooltip();4522}45234524void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)4525{4526bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);4527bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);4528if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))4529return;4530ImGuiContext& g = *GImGui;4531ImGuiColorEditFlags opts = g.ColorEditOptions;4532if (allow_opt_inputs)4533{4534if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;4535if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;4536if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;4537}4538if (allow_opt_datatype)4539{4540if (allow_opt_inputs) Separator();4541if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;4542if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;4543}45444545if (allow_opt_inputs || allow_opt_datatype)4546Separator();4547if (Button("Copy as..", ImVec2(-1,0)))4548OpenPopup("Copy");4549if (BeginPopup("Copy"))4550{4551int 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]);4552char buf[64];4553ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);4554if (Selectable(buf))4555SetClipboardText(buf);4556ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);4557if (Selectable(buf))4558SetClipboardText(buf);4559if (flags & ImGuiColorEditFlags_NoAlpha)4560ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);4561else4562ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);4563if (Selectable(buf))4564SetClipboardText(buf);4565EndPopup();4566}45674568g.ColorEditOptions = opts;4569EndPopup();4570}45714572void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)4573{4574bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);4575bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);4576if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))4577return;4578ImGuiContext& g = *GImGui;4579if (allow_opt_picker)4580{4581ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function4582PushItemWidth(picker_size.x);4583for (int picker_type = 0; picker_type < 2; picker_type++)4584{4585// Draw small/thumbnail version of each picker type (over an invisible button for selection)4586if (picker_type > 0) Separator();4587PushID(picker_type);4588ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);4589if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;4590if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;4591ImVec2 backup_pos = GetCursorScreenPos();4592if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup4593g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);4594SetCursorScreenPos(backup_pos);4595ImVec4 dummy_ref_col;4596memcpy(&dummy_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));4597ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);4598PopID();4599}4600PopItemWidth();4601}4602if (allow_opt_alpha_bar)4603{4604if (allow_opt_picker) Separator();4605CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);4606}4607EndPopup();4608}46094610//-------------------------------------------------------------------------4611// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.4612//-------------------------------------------------------------------------4613// - TreeNode()4614// - TreeNodeV()4615// - TreeNodeEx()4616// - TreeNodeExV()4617// - TreeNodeBehavior() [Internal]4618// - TreePush()4619// - TreePop()4620// - TreeAdvanceToLabelPos()4621// - GetTreeNodeToLabelSpacing()4622// - SetNextTreeNodeOpen()4623// - CollapsingHeader()4624//-------------------------------------------------------------------------46254626bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)4627{4628va_list args;4629va_start(args, fmt);4630bool is_open = TreeNodeExV(str_id, 0, fmt, args);4631va_end(args);4632return is_open;4633}46344635bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)4636{4637va_list args;4638va_start(args, fmt);4639bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);4640va_end(args);4641return is_open;4642}46434644bool ImGui::TreeNode(const char* label)4645{4646ImGuiWindow* window = GetCurrentWindow();4647if (window->SkipItems)4648return false;4649return TreeNodeBehavior(window->GetID(label), 0, label, NULL);4650}46514652bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)4653{4654return TreeNodeExV(str_id, 0, fmt, args);4655}46564657bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)4658{4659return TreeNodeExV(ptr_id, 0, fmt, args);4660}46614662bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)4663{4664ImGuiWindow* window = GetCurrentWindow();4665if (window->SkipItems)4666return false;46674668return TreeNodeBehavior(window->GetID(label), flags, label, NULL);4669}46704671bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)4672{4673va_list args;4674va_start(args, fmt);4675bool is_open = TreeNodeExV(str_id, flags, fmt, args);4676va_end(args);4677return is_open;4678}46794680bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)4681{4682va_list args;4683va_start(args, fmt);4684bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);4685va_end(args);4686return is_open;4687}46884689bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)4690{4691ImGuiWindow* window = GetCurrentWindow();4692if (window->SkipItems)4693return false;46944695ImGuiContext& g = *GImGui;4696const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);4697return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);4698}46994700bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)4701{4702ImGuiWindow* window = GetCurrentWindow();4703if (window->SkipItems)4704return false;47054706ImGuiContext& g = *GImGui;4707const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);4708return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);4709}47104711bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)4712{4713if (flags & ImGuiTreeNodeFlags_Leaf)4714return true;47154716// We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)4717ImGuiContext& g = *GImGui;4718ImGuiWindow* window = g.CurrentWindow;4719ImGuiStorage* storage = window->DC.StateStorage;47204721bool is_open;4722if (g.NextTreeNodeOpenCond != 0)4723{4724if (g.NextTreeNodeOpenCond & ImGuiCond_Always)4725{4726is_open = g.NextTreeNodeOpenVal;4727storage->SetInt(id, is_open);4728}4729else4730{4731// We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.4732const int stored_value = storage->GetInt(id, -1);4733if (stored_value == -1)4734{4735is_open = g.NextTreeNodeOpenVal;4736storage->SetInt(id, is_open);4737}4738else4739{4740is_open = stored_value != 0;4741}4742}4743g.NextTreeNodeOpenCond = 0;4744}4745else4746{4747is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;4748}47494750// When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).4751// NB- If we are above max depth we still allow manually opened nodes to be logged.4752if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)4753is_open = true;47544755return is_open;4756}47574758bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)4759{4760ImGuiWindow* window = GetCurrentWindow();4761if (window->SkipItems)4762return false;47634764ImGuiContext& g = *GImGui;4765const ImGuiStyle& style = g.Style;4766const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;4767const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);47684769if (!label_end)4770label_end = FindRenderedTextEnd(label);4771const ImVec2 label_size = CalcTextSize(label, label_end, false);47724773// We vertically grow up to current line height up the typical widget height.4774const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it4775const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);4776ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));4777if (display_frame)4778{4779// Framed header expand a little outside the default padding4780frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;4781frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;4782}47834784const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing4785const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser4786ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);47874788// For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing4789// (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)4790const 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);4791bool is_open = TreeNodeBehaviorIsOpen(id, flags);4792bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;47934794// Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.4795// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().4796// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.4797if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))4798window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);47994800bool item_add = ItemAdd(interact_bb, id);4801window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;4802window->DC.LastItemDisplayRect = frame_bb;48034804if (!item_add)4805{4806if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))4807TreePushRawID(id);4808IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));4809return is_open;4810}48114812// Flags that affects opening behavior:4813// - 0 (default) .................... single-click anywhere to open4814// - OpenOnDoubleClick .............. double-click anywhere to open4815// - OpenOnArrow .................... single-click on arrow to open4816// - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open4817ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers;4818if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)4819button_flags |= ImGuiButtonFlags_AllowItemOverlap;4820if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)4821button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);4822if (!is_leaf)4823button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;48244825bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;4826bool hovered, held;4827bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);4828bool toggled = false;4829if (!is_leaf)4830{4831if (pressed)4832{4833toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);4834if (flags & ImGuiTreeNodeFlags_OpenOnArrow)4835toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);4836if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)4837toggled |= g.IO.MouseDoubleClicked[0];4838if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.4839toggled = false;4840}48414842if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)4843{4844toggled = true;4845NavMoveRequestCancel();4846}4847if (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?4848{4849toggled = true;4850NavMoveRequestCancel();4851}48524853if (toggled)4854{4855is_open = !is_open;4856window->DC.StateStorage->SetInt(id, is_open);4857}4858}4859if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)4860SetItemAllowOverlap();48614862// Render4863const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);4864const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);4865ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;4866if (display_frame)4867{4868// Framed type4869RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);4870RenderNavHighlight(frame_bb, id, nav_highlight_flags);4871RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);4872if (g.LogEnabled)4873{4874// 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.4875const char log_prefix[] = "\n##";4876const char log_suffix[] = "##";4877LogRenderedText(&text_pos, log_prefix, log_prefix+3);4878RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);4879LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);4880}4881else4882{4883RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);4884}4885}4886else4887{4888// Unframed typed for tree nodes4889if (hovered || selected)4890{4891RenderFrame(frame_bb.Min, frame_bb.Max, col, false);4892RenderNavHighlight(frame_bb, id, nav_highlight_flags);4893}48944895if (flags & ImGuiTreeNodeFlags_Bullet)4896RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));4897else if (!is_leaf)4898RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);4899if (g.LogEnabled)4900LogRenderedText(&text_pos, ">");4901RenderText(text_pos, label, label_end, false);4902}49034904if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))4905TreePushRawID(id);4906IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));4907return is_open;4908}49094910void ImGui::TreePush(const char* str_id)4911{4912ImGuiWindow* window = GetCurrentWindow();4913Indent();4914window->DC.TreeDepth++;4915PushID(str_id ? str_id : "#TreePush");4916}49174918void ImGui::TreePush(const void* ptr_id)4919{4920ImGuiWindow* window = GetCurrentWindow();4921Indent();4922window->DC.TreeDepth++;4923PushID(ptr_id ? ptr_id : (const void*)"#TreePush");4924}49254926void ImGui::TreePushRawID(ImGuiID id)4927{4928ImGuiWindow* window = GetCurrentWindow();4929Indent();4930window->DC.TreeDepth++;4931window->IDStack.push_back(id);4932}49334934void ImGui::TreePop()4935{4936ImGuiContext& g = *GImGui;4937ImGuiWindow* window = g.CurrentWindow;4938Unindent();49394940window->DC.TreeDepth--;4941if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())4942if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))4943{4944SetNavID(window->IDStack.back(), g.NavLayer);4945NavMoveRequestCancel();4946}4947window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;49484949IM_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.4950PopID();4951}49524953void ImGui::TreeAdvanceToLabelPos()4954{4955ImGuiContext& g = *GImGui;4956g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();4957}49584959// Horizontal distance preceding label when using TreeNode() or Bullet()4960float ImGui::GetTreeNodeToLabelSpacing()4961{4962ImGuiContext& g = *GImGui;4963return g.FontSize + (g.Style.FramePadding.x * 2.0f);4964}49654966void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)4967{4968ImGuiContext& g = *GImGui;4969if (g.CurrentWindow->SkipItems)4970return;4971g.NextTreeNodeOpenVal = is_open;4972g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;4973}49744975// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).4976// 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().4977bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)4978{4979ImGuiWindow* window = GetCurrentWindow();4980if (window->SkipItems)4981return false;49824983return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);4984}49854986bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)4987{4988ImGuiWindow* window = GetCurrentWindow();4989if (window->SkipItems)4990return false;49914992if (p_open && !*p_open)4993return false;49944995ImGuiID id = window->GetID(label);4996bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);4997if (p_open)4998{4999// Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.5000ImGuiContext& g = *GImGui;5001ImGuiItemHoveredDataBackup last_item_backup;5002float button_radius = g.FontSize * 0.5f;5003ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);5004if (CloseButton(window->GetID((void*)((intptr_t)id+1)), button_center, button_radius))5005*p_open = false;5006last_item_backup.Restore();5007}50085009return is_open;5010}50115012//-------------------------------------------------------------------------5013// [SECTION] Widgets: Selectable5014//-------------------------------------------------------------------------5015// - Selectable()5016//-------------------------------------------------------------------------50175018// Tip: pass a non-visible label (e.g. "##dummy") then you can use the space to draw other text or image.5019// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.5020bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)5021{5022ImGuiWindow* window = GetCurrentWindow();5023if (window->SkipItems)5024return false;50255026ImGuiContext& g = *GImGui;5027const ImGuiStyle& style = g.Style;50285029if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.5030PopClipRect();50315032ImGuiID id = window->GetID(label);5033ImVec2 label_size = CalcTextSize(label, NULL, true);5034ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);5035ImVec2 pos = window->DC.CursorPos;5036pos.y += window->DC.CurrentLineTextBaseOffset;5037ImRect bb_inner(pos, pos + size);5038ItemSize(bb_inner);50395040// Fill horizontal space.5041ImVec2 window_padding = window->WindowPadding;5042float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;5043float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - pos.x);5044ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);5045ImRect bb(pos, pos + size_draw);5046if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))5047bb.Max.x += window_padding.x;50485049// Selectables are tightly packed together, we extend the box to cover spacing between selectable.5050float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);5051float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);5052float spacing_R = style.ItemSpacing.x - spacing_L;5053float spacing_D = style.ItemSpacing.y - spacing_U;5054bb.Min.x -= spacing_L;5055bb.Min.y -= spacing_U;5056bb.Max.x += spacing_R;5057bb.Max.y += spacing_D;5058if (!ItemAdd(bb, id))5059{5060if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)5061PushColumnClipRect();5062return false;5063}50645065// We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries5066ImGuiButtonFlags button_flags = 0;5067if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;5068if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;5069if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;5070if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;5071if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;5072if (flags & ImGuiSelectableFlags_Disabled)5073selected = false;50745075bool hovered, held;5076bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);5077// Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)5078if (pressed || hovered)5079if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)5080{5081g.NavDisableHighlight = true;5082SetNavID(id, window->DC.NavLayerCurrent);5083}5084if (pressed)5085MarkItemEdited(id);50865087// Render5088if (hovered || selected)5089{5090const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);5091RenderFrame(bb.Min, bb.Max, col, false, 0.0f);5092RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);5093}50945095if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)5096{5097PushColumnClipRect();5098bb.Max.x -= (GetContentRegionMax().x - max_x);5099}51005101if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);5102RenderTextClipped(bb_inner.Min, bb_inner.Max, label, NULL, &label_size, style.SelectableTextAlign, &bb);5103if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();51045105// Automatically close popups5106if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))5107CloseCurrentPopup();5108return pressed;5109}51105111bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)5112{5113if (Selectable(label, *p_selected, flags, size_arg))5114{5115*p_selected = !*p_selected;5116return true;5117}5118return false;5119}51205121//-------------------------------------------------------------------------5122// [SECTION] Widgets: ListBox5123//-------------------------------------------------------------------------5124// - ListBox()5125// - ListBoxHeader()5126// - ListBoxFooter()5127//-------------------------------------------------------------------------51285129// FIXME: In principle this function should be called BeginListBox(). We should rename it after re-evaluating if we want to keep the same signature.5130// Helper to calculate the size of a listbox and display a label on the right.5131// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"5132bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)5133{5134ImGuiWindow* window = GetCurrentWindow();5135if (window->SkipItems)5136return false;51375138const ImGuiStyle& style = GetStyle();5139const ImGuiID id = GetID(label);5140const ImVec2 label_size = CalcTextSize(label, NULL, true);51415142// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.5143ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);5144ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));5145ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);5146ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));5147window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.51485149if (!IsRectVisible(bb.Min, bb.Max))5150{5151ItemSize(bb.GetSize(), style.FramePadding.y);5152ItemAdd(bb, 0, &frame_bb);5153return false;5154}51555156BeginGroup();5157if (label_size.x > 0)5158RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);51595160BeginChildFrame(id, frame_bb.GetSize());5161return true;5162}51635164// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.5165bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)5166{5167// Size default to hold ~7.25 items.5168// 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.5169// 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.5170// I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.5171if (height_in_items < 0)5172height_in_items = ImMin(items_count, 7);5173const ImGuiStyle& style = GetStyle();5174float height_in_items_f = (height_in_items < items_count) ? (height_in_items + 0.25f) : (height_in_items + 0.00f);51755176// 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().5177ImVec2 size;5178size.x = 0.0f;5179size.y = GetTextLineHeightWithSpacing() * height_in_items_f + style.FramePadding.y * 2.0f;5180return ListBoxHeader(label, size);5181}51825183// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.5184void ImGui::ListBoxFooter()5185{5186ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;5187const ImRect bb = parent_window->DC.LastItemRect;5188const ImGuiStyle& style = GetStyle();51895190EndChildFrame();51915192// Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)5193// We call SameLine() to restore DC.CurrentLine* data5194SameLine();5195parent_window->DC.CursorPos = bb.Min;5196ItemSize(bb, style.FramePadding.y);5197EndGroup();5198}51995200bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)5201{5202const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);5203return value_changed;5204}52055206bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)5207{5208if (!ListBoxHeader(label, items_count, height_in_items))5209return false;52105211// 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.5212ImGuiContext& g = *GImGui;5213bool value_changed = false;5214ImGuiListClipper 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.5215while (clipper.Step())5216for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)5217{5218const bool item_selected = (i == *current_item);5219const char* item_text;5220if (!items_getter(data, i, &item_text))5221item_text = "*Unknown item*";52225223PushID(i);5224if (Selectable(item_text, item_selected))5225{5226*current_item = i;5227value_changed = true;5228}5229if (item_selected)5230SetItemDefaultFocus();5231PopID();5232}5233ListBoxFooter();5234if (value_changed)5235MarkItemEdited(g.CurrentWindow->DC.LastItemId);52365237return value_changed;5238}52395240//-------------------------------------------------------------------------5241// [SECTION] Widgets: PlotLines, PlotHistogram5242//-------------------------------------------------------------------------5243// - PlotEx() [Internal]5244// - PlotLines()5245// - PlotHistogram()5246//-------------------------------------------------------------------------52475248void 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)5249{5250ImGuiWindow* window = GetCurrentWindow();5251if (window->SkipItems)5252return;52535254ImGuiContext& g = *GImGui;5255const ImGuiStyle& style = g.Style;5256const ImGuiID id = window->GetID(label);52575258const ImVec2 label_size = CalcTextSize(label, NULL, true);5259if (frame_size.x == 0.0f)5260frame_size.x = CalcItemWidth();5261if (frame_size.y == 0.0f)5262frame_size.y = label_size.y + (style.FramePadding.y * 2);52635264const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);5265const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);5266const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));5267ItemSize(total_bb, style.FramePadding.y);5268if (!ItemAdd(total_bb, 0, &frame_bb))5269return;5270const bool hovered = ItemHoverable(frame_bb, id);52715272// Determine scale from values if not specified5273if (scale_min == FLT_MAX || scale_max == FLT_MAX)5274{5275float v_min = FLT_MAX;5276float v_max = -FLT_MAX;5277for (int i = 0; i < values_count; i++)5278{5279const float v = values_getter(data, i);5280v_min = ImMin(v_min, v);5281v_max = ImMax(v_max, v);5282}5283if (scale_min == FLT_MAX)5284scale_min = v_min;5285if (scale_max == FLT_MAX)5286scale_max = v_max;5287}52885289RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);52905291if (values_count > 0)5292{5293int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);5294int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);52955296// Tooltip on hover5297int v_hovered = -1;5298if (hovered && inner_bb.Contains(g.IO.MousePos))5299{5300const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);5301const int v_idx = (int)(t * item_count);5302IM_ASSERT(v_idx >= 0 && v_idx < values_count);53035304const float v0 = values_getter(data, (v_idx + values_offset) % values_count);5305const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);5306if (plot_type == ImGuiPlotType_Lines)5307SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);5308else if (plot_type == ImGuiPlotType_Histogram)5309SetTooltip("%d: %8.4g", v_idx, v0);5310v_hovered = v_idx;5311}53125313const float t_step = 1.0f / (float)res_w;5314const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));53155316float v0 = values_getter(data, (0 + values_offset) % values_count);5317float t0 = 0.0f;5318ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle5319float 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 stands53205321const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);5322const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);53235324for (int n = 0; n < res_w; n++)5325{5326const float t1 = t0 + t_step;5327const int v1_idx = (int)(t0 * item_count + 0.5f);5328IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);5329const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);5330const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );53315332// 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.5333ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);5334ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));5335if (plot_type == ImGuiPlotType_Lines)5336{5337window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);5338}5339else if (plot_type == ImGuiPlotType_Histogram)5340{5341if (pos1.x >= pos0.x + 2.0f)5342pos1.x -= 1.0f;5343window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);5344}53455346t0 = t1;5347tp0 = tp1;5348}5349}53505351// Text overlay5352if (overlay_text)5353RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));53545355if (label_size.x > 0.0f)5356RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);5357}53585359struct ImGuiPlotArrayGetterData5360{5361const float* Values;5362int Stride;53635364ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }5365};53665367static float Plot_ArrayGetter(void* data, int idx)5368{5369ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;5370const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);5371return v;5372}53735374void 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)5375{5376ImGuiPlotArrayGetterData data(values, stride);5377PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);5378}53795380void 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)5381{5382PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);5383}53845385void 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)5386{5387ImGuiPlotArrayGetterData data(values, stride);5388PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);5389}53905391void 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)5392{5393PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);5394}53955396//-------------------------------------------------------------------------5397// [SECTION] Widgets: Value helpers5398// Those is not very useful, legacy API.5399//-------------------------------------------------------------------------5400// - Value()5401//-------------------------------------------------------------------------54025403void ImGui::Value(const char* prefix, bool b)5404{5405Text("%s: %s", prefix, (b ? "true" : "false"));5406}54075408void ImGui::Value(const char* prefix, int v)5409{5410Text("%s: %d", prefix, v);5411}54125413void ImGui::Value(const char* prefix, unsigned int v)5414{5415Text("%s: %d", prefix, v);5416}54175418void ImGui::Value(const char* prefix, float v, const char* float_format)5419{5420if (float_format)5421{5422char fmt[64];5423ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);5424Text(fmt, prefix, v);5425}5426else5427{5428Text("%s: %.3f", prefix, v);5429}5430}54315432//-------------------------------------------------------------------------5433// [SECTION] MenuItem, BeginMenu, EndMenu, etc.5434//-------------------------------------------------------------------------5435// - ImGuiMenuColumns [Internal]5436// - BeginMainMenuBar()5437// - EndMainMenuBar()5438// - BeginMenuBar()5439// - EndMenuBar()5440// - BeginMenu()5441// - EndMenu()5442// - MenuItem()5443//-------------------------------------------------------------------------54445445// Helpers for internal use5446ImGuiMenuColumns::ImGuiMenuColumns()5447{5448Count = 0;5449Spacing = Width = NextWidth = 0.0f;5450memset(Pos, 0, sizeof(Pos));5451memset(NextWidths, 0, sizeof(NextWidths));5452}54535454void ImGuiMenuColumns::Update(int count, float spacing, bool clear)5455{5456IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));5457Count = count;5458Width = NextWidth = 0.0f;5459Spacing = spacing;5460if (clear) memset(NextWidths, 0, sizeof(NextWidths));5461for (int i = 0; i < Count; i++)5462{5463if (i > 0 && NextWidths[i] > 0.0f)5464Width += Spacing;5465Pos[i] = (float)(int)Width;5466Width += NextWidths[i];5467NextWidths[i] = 0.0f;5468}5469}54705471float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double5472{5473NextWidth = 0.0f;5474NextWidths[0] = ImMax(NextWidths[0], w0);5475NextWidths[1] = ImMax(NextWidths[1], w1);5476NextWidths[2] = ImMax(NextWidths[2], w2);5477for (int i = 0; i < 3; i++)5478NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);5479return ImMax(Width, NextWidth);5480}54815482float ImGuiMenuColumns::CalcExtraSpace(float avail_w)5483{5484return ImMax(0.0f, avail_w - Width);5485}54865487// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.5488bool ImGui::BeginMainMenuBar()5489{5490ImGuiContext& g = *GImGui;5491g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));5492SetNextWindowPos(ImVec2(0.0f, 0.0f));5493SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));5494PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);5495PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));5496ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;5497bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();5498PopStyleVar(2);5499g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);5500if (!is_open)5501{5502End();5503return false;5504}5505return true; //-V10205506}55075508void ImGui::EndMainMenuBar()5509{5510EndMenuBar();55115512// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window5513ImGuiContext& g = *GImGui;5514if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)5515FocusPreviousWindowIgnoringOne(g.NavWindow);55165517End();5518}55195520bool ImGui::BeginMenuBar()5521{5522ImGuiWindow* window = GetCurrentWindow();5523if (window->SkipItems)5524return false;5525if (!(window->Flags & ImGuiWindowFlags_MenuBar))5526return false;55275528IM_ASSERT(!window->DC.MenuBarAppending);5529BeginGroup(); // Backup position on layer 05530PushID("##menubar");55315532// 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.5533// 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.5534ImRect bar_rect = window->MenuBarRect();5535ImRect 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));5536clip_rect.ClipWith(window->OuterRectClipped);5537PushClipRect(clip_rect.Min, clip_rect.Max, false);55385539window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);5540window->DC.LayoutType = ImGuiLayoutType_Horizontal;5541window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;5542window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);5543window->DC.MenuBarAppending = true;5544AlignTextToFramePadding();5545return true;5546}55475548void ImGui::EndMenuBar()5549{5550ImGuiWindow* window = GetCurrentWindow();5551if (window->SkipItems)5552return;5553ImGuiContext& g = *GImGui;55545555// Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.5556if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))5557{5558ImGuiWindow* nav_earliest_child = g.NavWindow;5559while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))5560nav_earliest_child = nav_earliest_child->ParentWindow;5561if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)5562{5563// To do so we claim focus back, restore NavId and then process the movement request for yet another frame.5564// 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)5565IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check5566FocusWindow(window);5567SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);5568g.NavLayer = ImGuiNavLayer_Menu;5569g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.5570g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;5571NavMoveRequestCancel();5572}5573}55745575IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);5576IM_ASSERT(window->DC.MenuBarAppending);5577PopClipRect();5578PopID();5579window->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.5580window->DC.GroupStack.back().AdvanceCursor = false;5581EndGroup(); // Restore position on layer 05582window->DC.LayoutType = ImGuiLayoutType_Vertical;5583window->DC.NavLayerCurrent = ImGuiNavLayer_Main;5584window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);5585window->DC.MenuBarAppending = false;5586}55875588bool ImGui::BeginMenu(const char* label, bool enabled)5589{5590ImGuiWindow* window = GetCurrentWindow();5591if (window->SkipItems)5592return false;55935594ImGuiContext& g = *GImGui;5595const ImGuiStyle& style = g.Style;5596const ImGuiID id = window->GetID(label);55975598ImVec2 label_size = CalcTextSize(label, NULL, true);55995600bool pressed;5601bool menu_is_open = IsPopupOpen(id);5602bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());5603ImGuiWindow* backed_nav_window = g.NavWindow;5604if (menuset_is_open)5605g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)56065607// The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,5608// However the final position is going to be different! It is choosen by FindBestWindowPosForPopup().5609// e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.5610ImVec2 popup_pos, pos = window->DC.CursorPos;5611if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)5612{5613// Menu inside an horizontal menu bar5614// Selectable extend their highlight by half ItemSpacing in each direction.5615// For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()5616popup_pos = ImVec2(pos.x - 1.0f - (float)(int)(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());5617window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);5618PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);5619float w = label_size.x;5620pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));5621PopStyleVar();5622window->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().5623}5624else5625{5626// Menu inside a menu5627popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);5628float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame5629float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);5630pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));5631if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);5632RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);5633if (!enabled) PopStyleColor();5634}56355636const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);5637if (menuset_is_open)5638g.NavWindow = backed_nav_window;56395640bool want_open = false, want_close = false;5641if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))5642{5643// Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.5644bool moving_within_opened_triangle = false;5645if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))5646{5647if (ImGuiWindow* next_window = g.OpenPopupStack[g.BeginPopupStack.Size].Window)5648{5649// FIXME-DPI: Values should be derived from a master "scale" factor.5650ImRect next_window_rect = next_window->Rect();5651ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;5652ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();5653ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();5654float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.5655ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues5656tb.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?5657tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);5658moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);5659//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(); // Debug5660}5661}56625663want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);5664want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);56655666if (g.NavActivateId == id)5667{5668want_close = menu_is_open;5669want_open = !menu_is_open;5670}5671if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open5672{5673want_open = true;5674NavMoveRequestCancel();5675}5676}5677else5678{5679// Menu bar5680if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it5681{5682want_close = true;5683want_open = menu_is_open = false;5684}5685else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others5686{5687want_open = true;5688}5689else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open5690{5691want_open = true;5692NavMoveRequestCancel();5693}5694}56955696if (!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.. }'5697want_close = true;5698if (want_close && IsPopupOpen(id))5699ClosePopupToLevel(g.BeginPopupStack.Size, true);57005701IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));57025703if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)5704{5705// Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.5706OpenPopup(label);5707return false;5708}57095710menu_is_open |= want_open;5711if (want_open)5712OpenPopup(label);57135714if (menu_is_open)5715{5716// 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)5717SetNextWindowPos(popup_pos, ImGuiCond_Always);5718ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;5719if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))5720flags |= ImGuiWindowFlags_ChildWindow;5721menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)5722}57235724return menu_is_open;5725}57265727void ImGui::EndMenu()5728{5729// Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).5730// A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.5731// 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.5732ImGuiContext& g = *GImGui;5733ImGuiWindow* window = g.CurrentWindow;5734if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)5735{5736ClosePopupToLevel(g.BeginPopupStack.Size, true);5737NavMoveRequestCancel();5738}57395740EndPopup();5741}57425743bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)5744{5745ImGuiWindow* window = GetCurrentWindow();5746if (window->SkipItems)5747return false;57485749ImGuiContext& g = *GImGui;5750ImGuiStyle& style = g.Style;5751ImVec2 pos = window->DC.CursorPos;5752ImVec2 label_size = CalcTextSize(label, NULL, true);57535754ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);5755bool pressed;5756if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)5757{5758// Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful5759// Note that in this situation we render neither the shortcut neither the selected tick mark5760float w = label_size.x;5761window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);5762PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);5763pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));5764PopStyleVar();5765window->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().5766}5767else5768{5769ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);5770float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame5771float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);5772pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));5773if (shortcut_size.x > 0.0f)5774{5775PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);5776RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);5777PopStyleColor();5778}5779if (selected)5780RenderCheckMark(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);5781}57825783IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));5784return pressed;5785}57865787bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)5788{5789if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))5790{5791if (p_selected)5792*p_selected = !*p_selected;5793return true;5794}5795return false;5796}57975798//-------------------------------------------------------------------------5799// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.5800//-------------------------------------------------------------------------5801// [BETA API] API may evolve! This code has been extracted out of the Docking branch,5802// and some of the construct which are not used in Master may be left here to facilitate merging.5803//-------------------------------------------------------------------------5804// - BeginTabBar()5805// - BeginTabBarEx() [Internal]5806// - EndTabBar()5807// - TabBarLayout() [Internal]5808// - TabBarCalcTabID() [Internal]5809// - TabBarCalcMaxTabWidth() [Internal]5810// - TabBarFindTabById() [Internal]5811// - TabBarRemoveTab() [Internal]5812// - TabBarCloseTab() [Internal]5813// - TabBarScrollClamp()v5814// - TabBarScrollToTab() [Internal]5815// - TabBarQueueChangeTabOrder() [Internal]5816// - TabBarScrollingButtons() [Internal]5817// - TabBarTabListPopupButton() [Internal]5818//-------------------------------------------------------------------------58195820namespace ImGui5821{5822static void TabBarLayout(ImGuiTabBar* tab_bar);5823static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);5824static float TabBarCalcMaxTabWidth();5825static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);5826static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);5827static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar);5828static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar);5829}58305831ImGuiTabBar::ImGuiTabBar()5832{5833ID = 0;5834SelectedTabId = NextSelectedTabId = VisibleTabId = 0;5835CurrFrameVisible = PrevFrameVisible = -1;5836ContentsHeight = 0.0f;5837OffsetMax = OffsetNextTab = 0.0f;5838ScrollingAnim = ScrollingTarget = 0.0f;5839Flags = ImGuiTabBarFlags_None;5840ReorderRequestTabId = 0;5841ReorderRequestDir = 0;5842WantLayout = VisibleTabWasSubmitted = false;5843LastTabItemIdx = -1;5844}58455846static int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const void* rhs)5847{5848const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;5849const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;5850return (int)(a->Offset - b->Offset);5851}58525853static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs)5854{5855const ImGuiTabBarSortItem* a = (const ImGuiTabBarSortItem*)lhs;5856const ImGuiTabBarSortItem* b = (const ImGuiTabBarSortItem*)rhs;5857if (int d = (int)(b->Width - a->Width))5858return d;5859return (b->Index - a->Index);5860}58615862bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)5863{5864ImGuiContext& g = *GImGui;5865ImGuiWindow* window = g.CurrentWindow;5866if (window->SkipItems)5867return false;58685869ImGuiID id = window->GetID(str_id);5870ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);5871ImRect 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);5872tab_bar->ID = id;5873return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);5874}58755876bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)5877{5878ImGuiContext& g = *GImGui;5879ImGuiWindow* window = g.CurrentWindow;5880if (window->SkipItems)5881return false;58825883if ((flags & ImGuiTabBarFlags_DockNode) == 0)5884window->IDStack.push_back(tab_bar->ID);58855886g.CurrentTabBar.push_back(tab_bar);5887if (tab_bar->CurrFrameVisible == g.FrameCount)5888{5889//IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);5890IM_ASSERT(0);5891return true;5892}58935894// When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order.5895// 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.5896if ((flags & ImGuiTabBarFlags_Reorderable) && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1 && tab_bar->PrevFrameVisible != -1)5897ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByVisibleOffset);58985899// Flags5900if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)5901flags |= ImGuiTabBarFlags_FittingPolicyDefault_;59025903tab_bar->Flags = flags;5904tab_bar->BarRect = tab_bar_bb;5905tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()5906tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;5907tab_bar->CurrFrameVisible = g.FrameCount;5908tab_bar->FramePadding = g.Style.FramePadding;59095910// Layout5911ItemSize(ImVec2(tab_bar->OffsetMax, tab_bar->BarRect.GetHeight()));5912window->DC.CursorPos.x = tab_bar->BarRect.Min.x;59135914// Draw separator5915const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_Tab);5916const float y = tab_bar->BarRect.Max.y - 1.0f;5917{5918const float separator_min_x = tab_bar->BarRect.Min.x - window->WindowPadding.x;5919const float separator_max_x = tab_bar->BarRect.Max.x + window->WindowPadding.x;5920window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);5921}5922return true;5923}59245925void ImGui::EndTabBar()5926{5927ImGuiContext& g = *GImGui;5928ImGuiWindow* window = g.CurrentWindow;5929if (window->SkipItems)5930return;59315932IM_ASSERT(!g.CurrentTabBar.empty()); // Mismatched BeginTabBar/EndTabBar5933ImGuiTabBar* tab_bar = g.CurrentTabBar.back();5934if (tab_bar->WantLayout)5935TabBarLayout(tab_bar);59365937// Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().5938const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);5939if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)5940tab_bar->ContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, 0.0f);5941else5942window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->ContentsHeight;59435944if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)5945PopID();5946g.CurrentTabBar.pop_back();5947}59485949// This is called only once a frame before by the first call to ItemTab()5950// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.5951static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)5952{5953ImGuiContext& g = *GImGui;5954tab_bar->WantLayout = false;59555956// Garbage collect5957int tab_dst_n = 0;5958for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)5959{5960ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];5961if (tab->LastFrameVisible < tab_bar->PrevFrameVisible)5962{5963if (tab->ID == tab_bar->SelectedTabId)5964tab_bar->SelectedTabId = 0;5965continue;5966}5967if (tab_dst_n != tab_src_n)5968tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];5969tab_dst_n++;5970}5971if (tab_bar->Tabs.Size != tab_dst_n)5972tab_bar->Tabs.resize(tab_dst_n);59735974// Setup next selected tab5975ImGuiID scroll_track_selected_tab_id = 0;5976if (tab_bar->NextSelectedTabId)5977{5978tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;5979tab_bar->NextSelectedTabId = 0;5980scroll_track_selected_tab_id = tab_bar->SelectedTabId;5981}59825983// Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).5984if (tab_bar->ReorderRequestTabId != 0)5985{5986if (ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId))5987{5988//IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools5989int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir;5990if (tab2_order >= 0 && tab2_order < tab_bar->Tabs.Size)5991{5992ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];5993ImGuiTabItem item_tmp = *tab1;5994*tab1 = *tab2;5995*tab2 = item_tmp;5996if (tab2->ID == tab_bar->SelectedTabId)5997scroll_track_selected_tab_id = tab2->ID;5998tab1 = tab2 = NULL;5999}6000if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)6001MarkIniSettingsDirty();6002}6003tab_bar->ReorderRequestTabId = 0;6004}60056006// Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)6007const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;6008if (tab_list_popup_button)6009if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Max.x!6010scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;60116012ImVector<ImGuiTabBarSortItem>& width_sort_buffer = g.TabSortByWidthBuffer;6013width_sort_buffer.resize(tab_bar->Tabs.Size);60146015// Compute ideal widths6016float width_total_contents = 0.0f;6017ImGuiTabItem* most_recently_selected_tab = NULL;6018bool found_selected_tab_id = false;6019for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)6020{6021ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];6022IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);60236024if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)6025most_recently_selected_tab = tab;6026if (tab->ID == tab_bar->SelectedTabId)6027found_selected_tab_id = true;60286029// Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.6030// Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,6031// and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.6032const char* tab_name = tab_bar->GetTabName(tab);6033tab->WidthContents = TabItemCalcSize(tab_name, (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true).x;60346035width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->WidthContents;60366037// Store data so we can build an array sorted by width if we need to shrink tabs down6038width_sort_buffer[tab_n].Index = tab_n;6039width_sort_buffer[tab_n].Width = tab->WidthContents;6040}60416042// Compute width6043const float width_avail = tab_bar->BarRect.GetWidth();6044float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f;6045if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown))6046{6047// If we don't have enough room, resize down the largest tabs first6048if (tab_bar->Tabs.Size > 1)6049ImQsort(width_sort_buffer.Data, (size_t)width_sort_buffer.Size, sizeof(ImGuiTabBarSortItem), TabBarSortItemComparer);6050int tab_count_same_width = 1;6051while (width_excess > 0.0f && tab_count_same_width < tab_bar->Tabs.Size)6052{6053while (tab_count_same_width < tab_bar->Tabs.Size && width_sort_buffer[0].Width == width_sort_buffer[tab_count_same_width].Width)6054tab_count_same_width++;6055float 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);6056float width_to_remove_per_tab = ImMin(width_excess / tab_count_same_width, width_to_remove_per_tab_max);6057for (int tab_n = 0; tab_n < tab_count_same_width; tab_n++)6058width_sort_buffer[tab_n].Width -= width_to_remove_per_tab;6059width_excess -= width_to_remove_per_tab * tab_count_same_width;6060}6061for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)6062tab_bar->Tabs[width_sort_buffer[tab_n].Index].Width = (float)(int)width_sort_buffer[tab_n].Width;6063}6064else6065{6066const float tab_max_width = TabBarCalcMaxTabWidth();6067for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)6068{6069ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];6070tab->Width = ImMin(tab->WidthContents, tab_max_width);6071}6072}60736074// Layout all active tabs6075float offset_x = 0.0f;6076for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)6077{6078ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];6079tab->Offset = offset_x;6080if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID)6081scroll_track_selected_tab_id = tab->ID;6082offset_x += tab->Width + g.Style.ItemInnerSpacing.x;6083}6084tab_bar->OffsetMax = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f);6085tab_bar->OffsetNextTab = 0.0f;60866087// Horizontal scrolling buttons6088const 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);6089if (scrolling_buttons)6090if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x!6091scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;60926093// If we have lost the selected tab, select the next most recently active one6094if (found_selected_tab_id == false)6095tab_bar->SelectedTabId = 0;6096if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)6097scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;60986099// Lock in visible tab6100tab_bar->VisibleTabId = tab_bar->SelectedTabId;6101tab_bar->VisibleTabWasSubmitted = false;61026103// Update scrolling6104if (scroll_track_selected_tab_id)6105if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id))6106TabBarScrollToTab(tab_bar, scroll_track_selected_tab);6107tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);6108tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);6109const float scrolling_speed = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) ? FLT_MAX : (g.IO.DeltaTime * g.FontSize * 70.0f);6110if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)6111tab_bar->ScrollingAnim = ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, scrolling_speed);61126113// Clear name buffers6114if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)6115tab_bar->TabsNames.Buf.resize(0);6116}61176118// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.6119static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)6120{6121if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)6122{6123ImGuiID id = ImHashStr(label, 0);6124KeepAliveID(id);6125return id;6126}6127else6128{6129ImGuiWindow* window = GImGui->CurrentWindow;6130return window->GetID(label);6131}6132}61336134static float ImGui::TabBarCalcMaxTabWidth()6135{6136ImGuiContext& g = *GImGui;6137return g.FontSize * 20.0f;6138}61396140ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)6141{6142if (tab_id != 0)6143for (int n = 0; n < tab_bar->Tabs.Size; n++)6144if (tab_bar->Tabs[n].ID == tab_id)6145return &tab_bar->Tabs[n];6146return NULL;6147}61486149// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.6150void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)6151{6152if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))6153tab_bar->Tabs.erase(tab);6154if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; }6155if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; }6156if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }6157}61586159// Called on manual closure attempt6160void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)6161{6162if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))6163{6164// This will remove a frame of lag for selecting another tab on closure.6165// However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure6166tab->LastFrameVisible = -1;6167tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;6168}6169else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument))6170{6171// Actually select before expecting closure6172tab_bar->NextSelectedTabId = tab->ID;6173}6174}61756176static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)6177{6178scrolling = ImMin(scrolling, tab_bar->OffsetMax - tab_bar->BarRect.GetWidth());6179return ImMax(scrolling, 0.0f);6180}61816182static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)6183{6184ImGuiContext& g = *GImGui;6185float 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)6186int order = tab_bar->GetTabOrder(tab);6187float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f);6188float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f);6189if (tab_bar->ScrollingTarget > tab_x1)6190tab_bar->ScrollingTarget = tab_x1;6191if (tab_bar->ScrollingTarget + tab_bar->BarRect.GetWidth() < tab_x2)6192tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth();6193}61946195void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir)6196{6197IM_ASSERT(dir == -1 || dir == +1);6198IM_ASSERT(tab_bar->ReorderRequestTabId == 0);6199tab_bar->ReorderRequestTabId = tab->ID;6200tab_bar->ReorderRequestDir = dir;6201}62026203static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)6204{6205ImGuiContext& g = *GImGui;6206ImGuiWindow* window = g.CurrentWindow;62076208const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);6209const float scrolling_buttons_width = arrow_button_size.x * 2.0f;62106211const ImVec2 backup_cursor_pos = window->DC.CursorPos;6212//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));62136214const ImRect avail_bar_rect = tab_bar->BarRect;6215bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f)));6216if (want_clip_rect)6217PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true);62186219ImGuiTabItem* tab_to_select = NULL;62206221int select_dir = 0;6222ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];6223arrow_col.w *= 0.5f;62246225PushStyleColor(ImGuiCol_Text, arrow_col);6226PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));6227const float backup_repeat_delay = g.IO.KeyRepeatDelay;6228const float backup_repeat_rate = g.IO.KeyRepeatRate;6229g.IO.KeyRepeatDelay = 0.250f;6230g.IO.KeyRepeatRate = 0.200f;6231window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y);6232if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))6233select_dir = -1;6234window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y);6235if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))6236select_dir = +1;6237PopStyleColor(2);6238g.IO.KeyRepeatRate = backup_repeat_rate;6239g.IO.KeyRepeatDelay = backup_repeat_delay;62406241if (want_clip_rect)6242PopClipRect();62436244if (select_dir != 0)6245if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))6246{6247int selected_order = tab_bar->GetTabOrder(tab_item);6248int target_order = selected_order + select_dir;6249tab_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 visible6250}6251window->DC.CursorPos = backup_cursor_pos;6252tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;62536254return tab_to_select;6255}62566257static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)6258{6259ImGuiContext& g = *GImGui;6260ImGuiWindow* window = g.CurrentWindow;62616262// We use g.Style.FramePadding.y to match the square ArrowButton size6263const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;6264const ImVec2 backup_cursor_pos = window->DC.CursorPos;6265window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);6266tab_bar->BarRect.Min.x += tab_list_popup_button_width;62676268ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];6269arrow_col.w *= 0.5f;6270PushStyleColor(ImGuiCol_Text, arrow_col);6271PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));6272bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview);6273PopStyleColor(2);62746275ImGuiTabItem* tab_to_select = NULL;6276if (open)6277{6278for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)6279{6280ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];6281const char* tab_name = tab_bar->GetTabName(tab);6282if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))6283tab_to_select = tab;6284}6285EndCombo();6286}62876288window->DC.CursorPos = backup_cursor_pos;6289return tab_to_select;6290}62916292//-------------------------------------------------------------------------6293// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.6294//-------------------------------------------------------------------------6295// [BETA API] API may evolve! This code has been extracted out of the Docking branch,6296// and some of the construct which are not used in Master may be left here to facilitate merging.6297//-------------------------------------------------------------------------6298// - BeginTabItem()6299// - EndTabItem()6300// - TabItemEx() [Internal]6301// - SetTabItemClosed()6302// - TabItemCalcSize() [Internal]6303// - TabItemBackground() [Internal]6304// - TabItemLabelAndCloseButton() [Internal]6305//-------------------------------------------------------------------------63066307bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)6308{6309ImGuiContext& g = *GImGui;6310if (g.CurrentWindow->SkipItems)6311return false;63126313IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");6314ImGuiTabBar* tab_bar = g.CurrentTabBar.back();6315bool ret = TabItemEx(tab_bar, label, p_open, flags);6316if (ret && !(flags & ImGuiTabItemFlags_NoPushId))6317{6318ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];6319g.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)6320}6321return ret;6322}63236324void ImGui::EndTabItem()6325{6326ImGuiContext& g = *GImGui;6327if (g.CurrentWindow->SkipItems)6328return;63296330IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");6331ImGuiTabBar* tab_bar = g.CurrentTabBar.back();6332IM_ASSERT(tab_bar->LastTabItemIdx >= 0 && "Needs to be called between BeginTabItem() and EndTabItem()");6333ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];6334if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))6335g.CurrentWindow->IDStack.pop_back();6336}63376338bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)6339{6340// Layout whole tab bar if not already done6341if (tab_bar->WantLayout)6342TabBarLayout(tab_bar);63436344ImGuiContext& g = *GImGui;6345ImGuiWindow* window = g.CurrentWindow;6346if (window->SkipItems)6347return false;63486349const ImGuiStyle& style = g.Style;6350const ImGuiID id = TabBarCalcTabID(tab_bar, label);63516352// 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.6353if (p_open && !*p_open)6354{6355PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);6356ItemAdd(ImRect(), id);6357PopItemFlag();6358return false;6359}63606361// Calculate tab contents size6362ImVec2 size = TabItemCalcSize(label, p_open != NULL);63636364// Acquire tab data6365ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);6366bool tab_is_new = false;6367if (tab == NULL)6368{6369tab_bar->Tabs.push_back(ImGuiTabItem());6370tab = &tab_bar->Tabs.back();6371tab->ID = id;6372tab->Width = size.x;6373tab_is_new = true;6374}6375tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab);6376tab->WidthContents = size.x;63776378if (p_open == NULL)6379flags |= ImGuiTabItemFlags_NoCloseButton;63806381const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);6382const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;6383const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);6384tab->LastFrameVisible = g.FrameCount;6385tab->Flags = flags;63866387// Append name with zero-terminator6388tab->NameOffset = tab_bar->TabsNames.size();6389tab_bar->TabsNames.append(label, label + strlen(label) + 1);63906391// If we are not reorderable, always reset offset based on submission order.6392// (We already handled layout and sizing using the previous known order, but sizing is not affected by order!)6393if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable))6394{6395tab->Offset = tab_bar->OffsetNextTab;6396tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x;6397}63986399// Update selected tab6400if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)6401if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)6402tab_bar->NextSelectedTabId = id; // New tabs gets activated64036404// Lock visibility6405bool tab_contents_visible = (tab_bar->VisibleTabId == id);6406if (tab_contents_visible)6407tab_bar->VisibleTabWasSubmitted = true;64086409// On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches6410if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)6411if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))6412tab_contents_visible = true;64136414if (tab_appearing && !(tab_bar_appearing && !tab_is_new))6415{6416PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);6417ItemAdd(ImRect(), id);6418PopItemFlag();6419return tab_contents_visible;6420}64216422if (tab_bar->SelectedTabId == id)6423tab->LastFrameSelected = g.FrameCount;64246425// Backup current layout position6426const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;64276428// Layout6429size.x = tab->Width;6430window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2((float)(int)tab->Offset - tab_bar->ScrollingAnim, 0.0f);6431ImVec2 pos = window->DC.CursorPos;6432ImRect bb(pos, pos + size);64336434// 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)6435bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x >= tab_bar->BarRect.Max.x);6436if (want_clip_rect)6437PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x, bb.Max.y), true);64386439ItemSize(bb, style.FramePadding.y);6440if (!ItemAdd(bb, id))6441{6442if (want_clip_rect)6443PopClipRect();6444window->DC.CursorPos = backup_main_cursor_pos;6445return tab_contents_visible;6446}64476448// Click to Select a tab6449ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap);6450if (g.DragDropActive)6451button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;6452bool hovered, held;6453bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);6454hovered |= (g.HoveredId == id);6455if (pressed || ((flags & ImGuiTabItemFlags_SetSelected) && !tab_contents_visible)) // SetSelected can only be passed on explicit tab bar6456tab_bar->NextSelectedTabId = id;64576458// Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)6459if (!held)6460SetItemAllowOverlap();64616462// Drag and drop: re-order tabs6463if (held && !tab_appearing && IsMouseDragging(0))6464{6465if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))6466{6467// While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x6468if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)6469{6470if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)6471TabBarQueueChangeTabOrder(tab_bar, tab, -1);6472}6473else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)6474{6475if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)6476TabBarQueueChangeTabOrder(tab_bar, tab, +1);6477}6478}6479}64806481#if 06482if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->WidthContents)6483{6484// Enlarge tab display when hovering6485bb.Max.x = bb.Min.x + (float)(int)ImLerp(bb.GetWidth(), tab->WidthContents, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f));6486display_draw_list = GetOverlayDrawList(window);6487TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));6488}6489#endif64906491// Render tab shape6492ImDrawList* display_draw_list = window->DrawList;6493const 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));6494TabItemBackground(display_draw_list, bb, flags, tab_col);6495RenderNavHighlight(bb, id);64966497// Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.6498const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);6499if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))6500tab_bar->NextSelectedTabId = id;65016502if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)6503flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;65046505// Render tab label, process close button6506const ImGuiID close_button_id = p_open ? window->GetID((void*)((intptr_t)id + 1)) : 0;6507bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id);6508if (just_closed && p_open != NULL)6509{6510*p_open = false;6511TabBarCloseTab(tab_bar, tab);6512}65136514// Restore main window position so user can draw there6515if (want_clip_rect)6516PopClipRect();6517window->DC.CursorPos = backup_main_cursor_pos;65186519// Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)6520if (g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f)6521if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip))6522SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);65236524return tab_contents_visible;6525}65266527// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.6528// To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem()6529void ImGui::SetTabItemClosed(const char* label)6530{6531ImGuiContext& g = *GImGui;6532bool is_within_manual_tab_bar = (g.CurrentTabBar.Size > 0) && !(g.CurrentTabBar.back()->Flags & ImGuiTabBarFlags_DockNode);6533if (is_within_manual_tab_bar)6534{6535ImGuiTabBar* tab_bar = g.CurrentTabBar.back();6536IM_ASSERT(tab_bar->WantLayout); // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem()6537ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);6538TabBarRemoveTab(tab_bar, tab_id);6539}6540}65416542ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)6543{6544ImGuiContext& g = *GImGui;6545ImVec2 label_size = CalcTextSize(label, NULL, true);6546ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);6547if (has_close_button)6548size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.6549else6550size.x += g.Style.FramePadding.x + 1.0f;6551return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);6552}65536554void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)6555{6556// 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.6557ImGuiContext& g = *GImGui;6558const float width = bb.GetWidth();6559IM_UNUSED(flags);6560IM_ASSERT(width > 0.0f);6561const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f));6562const float y1 = bb.Min.y + 1.0f;6563const float y2 = bb.Max.y - 1.0f;6564draw_list->PathLineTo(ImVec2(bb.Min.x, y2));6565draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);6566draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);6567draw_list->PathLineTo(ImVec2(bb.Max.x, y2));6568draw_list->PathFillConvex(col);6569if (g.Style.TabBorderSize > 0.0f)6570{6571draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));6572draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);6573draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);6574draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));6575draw_list->PathStroke(GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize);6576}6577}65786579// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic6580// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.6581bool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id)6582{6583ImGuiContext& g = *GImGui;6584ImVec2 label_size = CalcTextSize(label, NULL, true);6585if (bb.GetWidth() <= 1.0f)6586return false;65876588// Render text label (with clipping + alpha gradient) + unsaved marker6589const char* TAB_UNSAVED_MARKER = "*";6590ImRect 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);6591if (flags & ImGuiTabItemFlags_UnsavedDocument)6592{6593text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;6594ImVec2 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));6595RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL);6596}6597ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;65986599// Close Button6600// We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()6601// 'hovered' will be true when hovering the Tab but NOT when hovering the close button6602// 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button6603// 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false6604bool close_button_pressed = false;6605bool close_button_visible = false;6606if (close_button_id != 0)6607if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == close_button_id)6608close_button_visible = true;6609if (close_button_visible)6610{6611ImGuiItemHoveredDataBackup last_item_backup;6612const float close_button_sz = g.FontSize * 0.5f;6613if (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))6614close_button_pressed = true;6615last_item_backup.Restore();66166617// Close with middle mouse button6618if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))6619close_button_pressed = true;66206621text_pixel_clip_bb.Max.x -= close_button_sz * 2.0f;6622}66236624// Label with ellipsis6625// 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 moment6626const char* label_display_end = FindRenderedTextEnd(label);6627if (label_size.x > text_ellipsis_clip_bb.GetWidth())6628{6629const int ellipsis_dot_count = 3;6630const float ellipsis_width = (1.0f + 1.0f) * ellipsis_dot_count - 1.0f;6631const char* label_end = NULL;6632float 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;6633if (label_end == label && label_end < label_display_end) // Always display at least 1 character if there's no room for character + ellipsis6634{6635label_end = label + ImTextCountUtf8BytesFromChar(label, label_display_end);6636label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label, label_end).x;6637}6638while (label_end > label && ImCharIsBlankA(label_end[-1])) // Trim trailing space6639{6640label_end--;6641label_size_clipped_x -= g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label_end, label_end + 1).x; // Ascii blanks are always 1 byte6642}6643RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_end, &label_size, ImVec2(0.0f, 0.0f));66446645const float ellipsis_x = text_pixel_clip_bb.Min.x + label_size_clipped_x + 1.0f;6646if (!close_button_visible && ellipsis_x + ellipsis_width <= bb.Max.x)6647RenderPixelEllipsis(draw_list, ImVec2(ellipsis_x, text_pixel_clip_bb.Min.y), ellipsis_dot_count, GetColorU32(ImGuiCol_Text));6648}6649else6650{6651RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_display_end, &label_size, ImVec2(0.0f, 0.0f));6652}66536654return close_button_pressed;6655}665666576658