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