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