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